fastapi-locale 0.1.0rc1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. fastapi_locale-0.1.0rc1/.gitignore +40 -0
  2. fastapi_locale-0.1.0rc1/CHANGELOG.md +25 -0
  3. fastapi_locale-0.1.0rc1/LICENSE +21 -0
  4. fastapi_locale-0.1.0rc1/PKG-INFO +91 -0
  5. fastapi_locale-0.1.0rc1/README.md +58 -0
  6. fastapi_locale-0.1.0rc1/docs/about/changelog.md +2 -0
  7. fastapi_locale-0.1.0rc1/docs/about/contributing.md +2 -0
  8. fastapi_locale-0.1.0rc1/docs/about/license.md +5 -0
  9. fastapi_locale-0.1.0rc1/docs/about/security.md +2 -0
  10. fastapi_locale-0.1.0rc1/docs/adr/0001-record-architecture-decisions.md +23 -0
  11. fastapi_locale-0.1.0rc1/docs/adr/0002-gettext-catalogs-through-babel.md +39 -0
  12. fastapi_locale-0.1.0rc1/docs/adr/0003-request-locale-in-a-context-variable.md +53 -0
  13. fastapi_locale-0.1.0rc1/docs/adr/0004-middleware-resolves-dependencies-refine.md +51 -0
  14. fastapi_locale-0.1.0rc1/docs/adr/0005-lazy-text-type.md +56 -0
  15. fastapi_locale-0.1.0rc1/docs/adr/0006-validation-messages-keyed-on-error-type.md +51 -0
  16. fastapi_locale-0.1.0rc1/docs/adr/0007-named-placeholder-formatting.md +42 -0
  17. fastapi_locale-0.1.0rc1/docs/adr/0008-load-catalogs-at-setup.md +37 -0
  18. fastapi_locale-0.1.0rc1/docs/adr/0009-localized-openapi.md +59 -0
  19. fastapi_locale-0.1.0rc1/docs/adr/0010-toolchain.md +46 -0
  20. fastapi_locale-0.1.0rc1/docs/adr/README.md +18 -0
  21. fastapi_locale-0.1.0rc1/docs/architecture/software-architecture.md +212 -0
  22. fastapi_locale-0.1.0rc1/docs/assets/favicon.svg +1 -0
  23. fastapi_locale-0.1.0rc1/docs/design/detailed-design.md +424 -0
  24. fastapi_locale-0.1.0rc1/docs/development/development-guide.md +171 -0
  25. fastapi_locale-0.1.0rc1/docs/diagrams/activity-accept-language.puml +26 -0
  26. fastapi_locale-0.1.0rc1/docs/diagrams/activity-accept-language.svg +1 -0
  27. fastapi_locale-0.1.0rc1/docs/diagrams/activity-catalog-workflow.puml +37 -0
  28. fastapi_locale-0.1.0rc1/docs/diagrams/activity-catalog-workflow.svg +1 -0
  29. fastapi_locale-0.1.0rc1/docs/diagrams/activity-locale-resolution.puml +31 -0
  30. fastapi_locale-0.1.0rc1/docs/diagrams/activity-locale-resolution.svg +1 -0
  31. fastapi_locale-0.1.0rc1/docs/diagrams/activity-message-lookup.puml +33 -0
  32. fastapi_locale-0.1.0rc1/docs/diagrams/activity-message-lookup.svg +1 -0
  33. fastapi_locale-0.1.0rc1/docs/diagrams/class-design.puml +142 -0
  34. fastapi_locale-0.1.0rc1/docs/diagrams/class-design.svg +1 -0
  35. fastapi_locale-0.1.0rc1/docs/diagrams/components.puml +62 -0
  36. fastapi_locale-0.1.0rc1/docs/diagrams/components.svg +1 -0
  37. fastapi_locale-0.1.0rc1/docs/diagrams/deployment.puml +34 -0
  38. fastapi_locale-0.1.0rc1/docs/diagrams/deployment.svg +1 -0
  39. fastapi_locale-0.1.0rc1/docs/diagrams/domain-model.puml +125 -0
  40. fastapi_locale-0.1.0rc1/docs/diagrams/domain-model.svg +1 -0
  41. fastapi_locale-0.1.0rc1/docs/diagrams/include/style.iuml +21 -0
  42. fastapi_locale-0.1.0rc1/docs/diagrams/module-dependencies.puml +75 -0
  43. fastapi_locale-0.1.0rc1/docs/diagrams/module-dependencies.svg +1 -0
  44. fastapi_locale-0.1.0rc1/docs/diagrams/sequence-lazy-text.puml +40 -0
  45. fastapi_locale-0.1.0rc1/docs/diagrams/sequence-lazy-text.svg +1 -0
  46. fastapi_locale-0.1.0rc1/docs/diagrams/sequence-request.puml +46 -0
  47. fastapi_locale-0.1.0rc1/docs/diagrams/sequence-request.svg +1 -0
  48. fastapi_locale-0.1.0rc1/docs/diagrams/sequence-startup.puml +52 -0
  49. fastapi_locale-0.1.0rc1/docs/diagrams/sequence-startup.svg +1 -0
  50. fastapi_locale-0.1.0rc1/docs/diagrams/sequence-validation-error.puml +54 -0
  51. fastapi_locale-0.1.0rc1/docs/diagrams/sequence-validation-error.svg +1 -0
  52. fastapi_locale-0.1.0rc1/docs/diagrams/state-catalog-store.puml +16 -0
  53. fastapi_locale-0.1.0rc1/docs/diagrams/state-catalog-store.svg +1 -0
  54. fastapi_locale-0.1.0rc1/docs/diagrams/state-request-locale.puml +29 -0
  55. fastapi_locale-0.1.0rc1/docs/diagrams/state-request-locale.svg +1 -0
  56. fastapi_locale-0.1.0rc1/docs/diagrams/system-context.puml +45 -0
  57. fastapi_locale-0.1.0rc1/docs/diagrams/system-context.svg +1 -0
  58. fastapi_locale-0.1.0rc1/docs/diagrams/use-case.puml +40 -0
  59. fastapi_locale-0.1.0rc1/docs/diagrams/use-case.svg +1 -0
  60. fastapi_locale-0.1.0rc1/docs/getting-started/installation.md +32 -0
  61. fastapi_locale-0.1.0rc1/docs/getting-started/quickstart.md +103 -0
  62. fastapi_locale-0.1.0rc1/docs/getting-started/tutorial.md +195 -0
  63. fastapi_locale-0.1.0rc1/docs/guide/api-documentation.md +62 -0
  64. fastapi_locale-0.1.0rc1/docs/guide/command-line.md +31 -0
  65. fastapi_locale-0.1.0rc1/docs/guide/configuration.md +56 -0
  66. fastapi_locale-0.1.0rc1/docs/guide/deployment.md +53 -0
  67. fastapi_locale-0.1.0rc1/docs/guide/lazy-text.md +51 -0
  68. fastapi_locale-0.1.0rc1/docs/guide/locale-resolution.md +45 -0
  69. fastapi_locale-0.1.0rc1/docs/guide/migration.md +50 -0
  70. fastapi_locale-0.1.0rc1/docs/guide/testing.md +46 -0
  71. fastapi_locale-0.1.0rc1/docs/guide/translating.md +49 -0
  72. fastapi_locale-0.1.0rc1/docs/guide/troubleshooting.md +47 -0
  73. fastapi_locale-0.1.0rc1/docs/guide/user-language.md +48 -0
  74. fastapi_locale-0.1.0rc1/docs/guide/validation-errors.md +65 -0
  75. fastapi_locale-0.1.0rc1/docs/index.md +57 -0
  76. fastapi_locale-0.1.0rc1/docs/modeling/domain-model.md +127 -0
  77. fastapi_locale-0.1.0rc1/docs/modeling/use-case-model.md +234 -0
  78. fastapi_locale-0.1.0rc1/docs/project-documents.md +47 -0
  79. fastapi_locale-0.1.0rc1/docs/reference/context.md +9 -0
  80. fastapi_locale-0.1.0rc1/docs/reference/dependencies.md +9 -0
  81. fastapi_locale-0.1.0rc1/docs/reference/errors.md +17 -0
  82. fastapi_locale-0.1.0rc1/docs/reference/lazy.md +11 -0
  83. fastapi_locale-0.1.0rc1/docs/reference/setup.md +11 -0
  84. fastapi_locale-0.1.0rc1/docs/reference/sources.md +9 -0
  85. fastapi_locale-0.1.0rc1/docs/reference/testing.md +3 -0
  86. fastapi_locale-0.1.0rc1/docs/reference/translation.md +23 -0
  87. fastapi_locale-0.1.0rc1/docs/requirements/software-requirements-specification.md +281 -0
  88. fastapi_locale-0.1.0rc1/docs/research/existing-libraries.md +159 -0
  89. fastapi_locale-0.1.0rc1/docs/testing/test-plan.md +155 -0
  90. fastapi_locale-0.1.0rc1/examples/basic/README.md +21 -0
  91. fastapi_locale-0.1.0rc1/examples/basic/app.py +82 -0
  92. fastapi_locale-0.1.0rc1/examples/basic/locales/de/LC_MESSAGES/messages.po +55 -0
  93. fastapi_locale-0.1.0rc1/examples/basic/locales/hi/LC_MESSAGES/messages.po +55 -0
  94. fastapi_locale-0.1.0rc1/examples/basic/locales/messages.pot +53 -0
  95. fastapi_locale-0.1.0rc1/examples/basic/pyproject.toml +5 -0
  96. fastapi_locale-0.1.0rc1/examples/basic/uv.lock +3 -0
  97. fastapi_locale-0.1.0rc1/pyproject.toml +223 -0
  98. fastapi_locale-0.1.0rc1/src/fastapi_locale/__init__.py +98 -0
  99. fastapi_locale-0.1.0rc1/src/fastapi_locale/_accept_language.py +52 -0
  100. fastapi_locale-0.1.0rc1/src/fastapi_locale/_catalog.py +244 -0
  101. fastapi_locale-0.1.0rc1/src/fastapi_locale/_context.py +224 -0
  102. fastapi_locale-0.1.0rc1/src/fastapi_locale/_errors_catalog.py +281 -0
  103. fastapi_locale-0.1.0rc1/src/fastapi_locale/_formatting.py +45 -0
  104. fastapi_locale-0.1.0rc1/src/fastapi_locale/_locale.py +100 -0
  105. fastapi_locale-0.1.0rc1/src/fastapi_locale/_openapi.py +71 -0
  106. fastapi_locale-0.1.0rc1/src/fastapi_locale/_translator.py +171 -0
  107. fastapi_locale-0.1.0rc1/src/fastapi_locale/cli/__init__.py +74 -0
  108. fastapi_locale-0.1.0rc1/src/fastapi_locale/cli/__main__.py +5 -0
  109. fastapi_locale-0.1.0rc1/src/fastapi_locale/cli/commands.py +237 -0
  110. fastapi_locale-0.1.0rc1/src/fastapi_locale/cli/settings.py +77 -0
  111. fastapi_locale-0.1.0rc1/src/fastapi_locale/config.py +82 -0
  112. fastapi_locale-0.1.0rc1/src/fastapi_locale/dependencies.py +29 -0
  113. fastapi_locale-0.1.0rc1/src/fastapi_locale/exceptions.py +31 -0
  114. fastapi_locale-0.1.0rc1/src/fastapi_locale/handlers.py +49 -0
  115. fastapi_locale-0.1.0rc1/src/fastapi_locale/lazy.py +139 -0
  116. fastapi_locale-0.1.0rc1/src/fastapi_locale/locales/de/LC_MESSAGES/fastapi_locale.po +556 -0
  117. fastapi_locale-0.1.0rc1/src/fastapi_locale/locales/es/LC_MESSAGES/fastapi_locale.po +554 -0
  118. fastapi_locale-0.1.0rc1/src/fastapi_locale/locales/fastapi_locale.pot +534 -0
  119. fastapi_locale-0.1.0rc1/src/fastapi_locale/locales/fr/LC_MESSAGES/fastapi_locale.po +552 -0
  120. fastapi_locale-0.1.0rc1/src/fastapi_locale/locales/hi/LC_MESSAGES/fastapi_locale.po +545 -0
  121. fastapi_locale-0.1.0rc1/src/fastapi_locale/locales/pt_BR/LC_MESSAGES/fastapi_locale.po +546 -0
  122. fastapi_locale-0.1.0rc1/src/fastapi_locale/localization.py +161 -0
  123. fastapi_locale-0.1.0rc1/src/fastapi_locale/middleware.py +66 -0
  124. fastapi_locale-0.1.0rc1/src/fastapi_locale/openapi.py +34 -0
  125. fastapi_locale-0.1.0rc1/src/fastapi_locale/py.typed +0 -0
  126. fastapi_locale-0.1.0rc1/src/fastapi_locale/sources.py +102 -0
  127. fastapi_locale-0.1.0rc1/src/fastapi_locale/testing.py +30 -0
  128. fastapi_locale-0.1.0rc1/tests/__init__.py +0 -0
  129. fastapi_locale-0.1.0rc1/tests/benchmark/__init__.py +0 -0
  130. fastapi_locale-0.1.0rc1/tests/benchmark/test_budget.py +39 -0
  131. fastapi_locale-0.1.0rc1/tests/conftest.py +100 -0
  132. fastapi_locale-0.1.0rc1/tests/data/locales/ar/LC_MESSAGES/messages.po +28 -0
  133. fastapi_locale-0.1.0rc1/tests/data/locales/de/LC_MESSAGES/admin.po +7 -0
  134. fastapi_locale-0.1.0rc1/tests/data/locales/de/LC_MESSAGES/fastapi_locale.po +8 -0
  135. fastapi_locale-0.1.0rc1/tests/data/locales/de/LC_MESSAGES/messages.po +46 -0
  136. fastapi_locale-0.1.0rc1/tests/data/locales/fr/LC_MESSAGES/messages.po +24 -0
  137. fastapi_locale-0.1.0rc1/tests/data/locales/hi/LC_MESSAGES/messages.po +27 -0
  138. fastapi_locale-0.1.0rc1/tests/data/locales/ja/LC_MESSAGES/messages.po +23 -0
  139. fastapi_locale-0.1.0rc1/tests/data/locales/pt/LC_MESSAGES/messages.po +27 -0
  140. fastapi_locale-0.1.0rc1/tests/data/locales/pt_BR/LC_MESSAGES/messages.po +8 -0
  141. fastapi_locale-0.1.0rc1/tests/data/locales/ru/LC_MESSAGES/messages.po +25 -0
  142. fastapi_locale-0.1.0rc1/tests/e2e/__init__.py +0 -0
  143. fastapi_locale-0.1.0rc1/tests/e2e/conftest.py +72 -0
  144. fastapi_locale-0.1.0rc1/tests/e2e/test_cli.py +180 -0
  145. fastapi_locale-0.1.0rc1/tests/e2e/test_server.py +78 -0
  146. fastapi_locale-0.1.0rc1/tests/integration/__init__.py +0 -0
  147. fastapi_locale-0.1.0rc1/tests/integration/test_background.py +18 -0
  148. fastapi_locale-0.1.0rc1/tests/integration/test_dependencies.py +26 -0
  149. fastapi_locale-0.1.0rc1/tests/integration/test_headers.py +68 -0
  150. fastapi_locale-0.1.0rc1/tests/integration/test_http_errors.py +44 -0
  151. fastapi_locale-0.1.0rc1/tests/integration/test_isolation.py +37 -0
  152. fastapi_locale-0.1.0rc1/tests/integration/test_lazy_responses.py +41 -0
  153. fastapi_locale-0.1.0rc1/tests/integration/test_openapi.py +62 -0
  154. fastapi_locale-0.1.0rc1/tests/integration/test_resolution.py +88 -0
  155. fastapi_locale-0.1.0rc1/tests/integration/test_setup.py +90 -0
  156. fastapi_locale-0.1.0rc1/tests/integration/test_testing_helpers.py +41 -0
  157. fastapi_locale-0.1.0rc1/tests/integration/test_user_locale.py +63 -0
  158. fastapi_locale-0.1.0rc1/tests/integration/test_validation_errors.py +117 -0
  159. fastapi_locale-0.1.0rc1/tests/integration/test_websocket.py +17 -0
  160. fastapi_locale-0.1.0rc1/tests/unit/__init__.py +0 -0
  161. fastapi_locale-0.1.0rc1/tests/unit/test_accept_language.py +53 -0
  162. fastapi_locale-0.1.0rc1/tests/unit/test_catalog.py +148 -0
  163. fastapi_locale-0.1.0rc1/tests/unit/test_config.py +51 -0
  164. fastapi_locale-0.1.0rc1/tests/unit/test_context.py +103 -0
  165. fastapi_locale-0.1.0rc1/tests/unit/test_error_localizer.py +112 -0
  166. fastapi_locale-0.1.0rc1/tests/unit/test_formatting.py +42 -0
  167. fastapi_locale-0.1.0rc1/tests/unit/test_lazy.py +96 -0
  168. fastapi_locale-0.1.0rc1/tests/unit/test_locale.py +88 -0
  169. fastapi_locale-0.1.0rc1/tests/unit/test_openapi.py +72 -0
  170. fastapi_locale-0.1.0rc1/tests/unit/test_translator.py +111 -0
@@ -0,0 +1,40 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+
5
+ # Packaging
6
+ build/
7
+ dist/
8
+ *.egg-info/
9
+
10
+ # Environments
11
+ .venv/
12
+ .env
13
+ .env.*
14
+
15
+ # Tests and tools
16
+ .pytest_cache/
17
+ .coverage
18
+ .coverage.*
19
+ coverage.xml
20
+ htmlcov/
21
+ .hypothesis/
22
+ .benchmarks/
23
+ .mypy_cache/
24
+ .ruff_cache/
25
+ .import_linter_cache/
26
+
27
+ # Compiled catalogs are build output; .po files are the source
28
+ *.mo
29
+
30
+ # Documentation build
31
+ site/
32
+
33
+ # Editors and OS
34
+ .vscode/
35
+ .idea/
36
+ *.swp
37
+ .DS_Store
38
+
39
+ # Local scratch files and visual aids, never committed
40
+ .tmp/
@@ -0,0 +1,25 @@
1
+ # Changelog
2
+
3
+ All notable changes are listed here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project uses
5
+ [semantic versioning](https://semver.org).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.1.0rc1] - 2026-09-28
10
+
11
+ First release candidate. Installs only with `pip install --pre fastapi-locale` or an exact version.
12
+
13
+ ### Added
14
+
15
+ - Per-request locale from query parameter, cookie and `Accept-Language`, with RFC 4647 matching and
16
+ `Content-Language` and `Vary` response headers.
17
+ - gettext, ngettext, pgettext, npgettext and their domain variants, with named placeholders.
18
+ - `LazyText` for translatable text in Pydantic models, response models and `HTTPException.detail`.
19
+ - Localized 422 validation errors for every Pydantic error type, with built-in German, Spanish, French,
20
+ Hindi and Portuguese (Brazil) translations.
21
+ - `set_locale()` to apply a signed-in user's language for the rest of a request.
22
+ - `LocaleDep` and `TranslatorDep` dependencies, `use_locale()`, and test helpers.
23
+ - `fastapi-locale` command: extract, init, update, compile and check.
24
+ - Localized OpenAPI schema: Swagger UI and ReDoc follow the request locale, with built-in
25
+ translations of FastAPI's own schema text. Turn off with `localize_openapi=False`.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 fastapi-locale contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,91 @@
1
+ Metadata-Version: 2.5
2
+ Name: fastapi-locale
3
+ Version: 0.1.0rc1
4
+ Summary: Internationalization for FastAPI: gettext catalogs, per-request locales, lazy text and localized validation errors.
5
+ Project-URL: Homepage, https://github.com/fastapi-extensions/fastapi-locale
6
+ Project-URL: Documentation, https://fastapi-locale.readthedocs.io
7
+ Project-URL: Repository, https://github.com/fastapi-extensions/fastapi-locale
8
+ Project-URL: Bug Tracker, https://github.com/fastapi-extensions/fastapi-locale/issues
9
+ Project-URL: Changelog, https://github.com/fastapi-extensions/fastapi-locale/blob/main/CHANGELOG.md
10
+ Author: fastapi-locale contributors
11
+ License-Expression: MIT
12
+ License-File: LICENSE
13
+ Keywords: babel,fastapi,gettext,i18n,internationalization,l10n,localization,pydantic
14
+ Classifier: Development Status :: 3 - Alpha
15
+ Classifier: Framework :: FastAPI
16
+ Classifier: Framework :: Pydantic :: 2
17
+ Classifier: Intended Audience :: Developers
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Programming Language :: Python :: 3.14
24
+ Classifier: Topic :: Internet :: WWW/HTTP
25
+ Classifier: Topic :: Software Development :: Internationalization
26
+ Classifier: Topic :: Software Development :: Localization
27
+ Classifier: Typing :: Typed
28
+ Requires-Python: >=3.11
29
+ Requires-Dist: babel>=2.12.0
30
+ Requires-Dist: fastapi>=0.115.0
31
+ Requires-Dist: pydantic>=2.7.0
32
+ Description-Content-Type: text/markdown
33
+
34
+ # fastapi-locale
35
+
36
+ Internationalization for [FastAPI](https://fastapi.tiangolo.com), built on standard gettext catalogs.
37
+
38
+ - Picks a locale for every request from a query parameter, cookie or `Accept-Language`, with RFC 4647
39
+ matching and correct `Content-Language` and `Vary` headers.
40
+ - Returns FastAPI's 422 validation errors in the client's language, with correct plural forms.
41
+ - Lazy text that works in Pydantic models, response models and `HTTPException.detail`.
42
+ - Dependency injection first: `LocaleDep` and `TranslatorDep`, replaceable in tests.
43
+ - Lets a dependency switch to the signed-in user's saved language for the rest of the request.
44
+ - A command line tool to extract, update, compile and check catalogs in CI.
45
+
46
+ > **Status:** in development, not yet released. The design is in [docs/](docs/project-documents.md).
47
+
48
+ ## Quick start
49
+
50
+ ```python
51
+ from fastapi import FastAPI, HTTPException
52
+
53
+ from fastapi_locale import LocaleConfig, Localization, TranslatorDep, gettext_lazy
54
+
55
+ i18n = Localization(
56
+ LocaleConfig(default_locale="en", supported_locales=["en", "hi", "de"], catalog_dirs=["locales"])
57
+ )
58
+ app = FastAPI()
59
+ i18n.install(app)
60
+
61
+ NOT_FOUND = gettext_lazy("Item not found")
62
+
63
+
64
+ @app.get("/items/{item_id}")
65
+ def read_item(item_id: int, tr: TranslatorDep) -> dict[str, str]:
66
+ if item_id != 1:
67
+ raise HTTPException(status_code=404, detail=NOT_FOUND)
68
+ return {"name": tr.gettext("Sample item")}
69
+ ```
70
+
71
+ A request with `Accept-Language: de` now gets German text, German validation errors and
72
+ `Content-Language: de`.
73
+
74
+ ## Translation workflow
75
+
76
+ ```sh
77
+ fastapi-locale extract # scan the code, write locales/messages.pot
78
+ fastapi-locale init --locale hi # start a new language
79
+ fastapi-locale update # merge new messages into every .po file
80
+ fastapi-locale compile # build .mo files
81
+ fastapi-locale check # fail CI when catalogs are stale or broken
82
+ ```
83
+
84
+ ## Contributing
85
+
86
+ See [CONTRIBUTING.md](CONTRIBUTING.md). The engineering documents (requirements, models, architecture,
87
+ decisions and test plan) are in [docs/](docs/project-documents.md).
88
+
89
+ ## License
90
+
91
+ MIT
@@ -0,0 +1,58 @@
1
+ # fastapi-locale
2
+
3
+ Internationalization for [FastAPI](https://fastapi.tiangolo.com), built on standard gettext catalogs.
4
+
5
+ - Picks a locale for every request from a query parameter, cookie or `Accept-Language`, with RFC 4647
6
+ matching and correct `Content-Language` and `Vary` headers.
7
+ - Returns FastAPI's 422 validation errors in the client's language, with correct plural forms.
8
+ - Lazy text that works in Pydantic models, response models and `HTTPException.detail`.
9
+ - Dependency injection first: `LocaleDep` and `TranslatorDep`, replaceable in tests.
10
+ - Lets a dependency switch to the signed-in user's saved language for the rest of the request.
11
+ - A command line tool to extract, update, compile and check catalogs in CI.
12
+
13
+ > **Status:** in development, not yet released. The design is in [docs/](docs/project-documents.md).
14
+
15
+ ## Quick start
16
+
17
+ ```python
18
+ from fastapi import FastAPI, HTTPException
19
+
20
+ from fastapi_locale import LocaleConfig, Localization, TranslatorDep, gettext_lazy
21
+
22
+ i18n = Localization(
23
+ LocaleConfig(default_locale="en", supported_locales=["en", "hi", "de"], catalog_dirs=["locales"])
24
+ )
25
+ app = FastAPI()
26
+ i18n.install(app)
27
+
28
+ NOT_FOUND = gettext_lazy("Item not found")
29
+
30
+
31
+ @app.get("/items/{item_id}")
32
+ def read_item(item_id: int, tr: TranslatorDep) -> dict[str, str]:
33
+ if item_id != 1:
34
+ raise HTTPException(status_code=404, detail=NOT_FOUND)
35
+ return {"name": tr.gettext("Sample item")}
36
+ ```
37
+
38
+ A request with `Accept-Language: de` now gets German text, German validation errors and
39
+ `Content-Language: de`.
40
+
41
+ ## Translation workflow
42
+
43
+ ```sh
44
+ fastapi-locale extract # scan the code, write locales/messages.pot
45
+ fastapi-locale init --locale hi # start a new language
46
+ fastapi-locale update # merge new messages into every .po file
47
+ fastapi-locale compile # build .mo files
48
+ fastapi-locale check # fail CI when catalogs are stale or broken
49
+ ```
50
+
51
+ ## Contributing
52
+
53
+ See [CONTRIBUTING.md](CONTRIBUTING.md). The engineering documents (requirements, models, architecture,
54
+ decisions and test plan) are in [docs/](docs/project-documents.md).
55
+
56
+ ## License
57
+
58
+ MIT
@@ -0,0 +1,2 @@
1
+ <!-- markdownlint-disable-file MD041 -->
2
+ --8<-- "CHANGELOG.md"
@@ -0,0 +1,2 @@
1
+ <!-- markdownlint-disable-file MD041 -->
2
+ --8<-- "CONTRIBUTING.md"
@@ -0,0 +1,5 @@
1
+ # License
2
+
3
+ ```text
4
+ --8<-- "LICENSE"
5
+ ```
@@ -0,0 +1,2 @@
1
+ <!-- markdownlint-disable-file MD041 -->
2
+ --8<-- "SECURITY.md"
@@ -0,0 +1,23 @@
1
+ # ADR-0001: Record architecture decisions
2
+
3
+ - Status: Accepted
4
+ - Date: 2026-09-26
5
+
6
+ ## Context
7
+
8
+ The library will be maintained in the open, and contributors will ask why it works the way it does. Those
9
+ answers should not live only in issue threads or in one person's head.
10
+
11
+ ## Decision
12
+
13
+ Record each significant decision as a short Markdown file in `docs/adr/`, numbered in order. Each record
14
+ has these sections: Status, Date, Context, Options considered, Decision, Consequences, and Related
15
+ requirements where they apply.
16
+
17
+ Status is one of Proposed, Accepted, Superseded by ADR-NNNN, or Rejected. An accepted record is not edited
18
+ except to change its status; a new record replaces it.
19
+
20
+ ## Consequences
21
+
22
+ - Reviewers can check a pull request against the recorded decisions.
23
+ - Changing a decision takes a new record, which makes the change and its reasons visible.
@@ -0,0 +1,39 @@
1
+ # ADR-0002: Use GNU gettext catalogs loaded through Babel
2
+
3
+ - Status: Accepted
4
+ - Accepted: 2026-09-28
5
+ - Date: 2026-09-26
6
+
7
+ ## Context
8
+
9
+ Translations need a storage format that translators already know, that handles plural forms correctly for
10
+ every language, and that has tooling for extracting messages from Python code.
11
+
12
+ ## Options considered
13
+
14
+ 1. **GNU gettext (`.po` / `.mo`) read with Babel.** The standard in Python (Django, Flask-Babel, CPython
15
+ itself). Supported by Poedit, Weblate, Crowdin, Transifex and Lokalise. Babel adds CLDR plural rules,
16
+ message extraction and compilation.
17
+ 2. **gettext through the standard library only.** No extra dependency, but no extraction tooling, no CLDR
18
+ data and no locale formatting later.
19
+ 3. **JSON or YAML catalogs.** Easy to read, but every project invents its own plural and context
20
+ conventions, and the translation tools support them unevenly.
21
+ 4. **Fluent (Project Fluent).** Expressive, but little adoption in the Python web world and a heavier
22
+ runtime.
23
+
24
+ ## Decision
25
+
26
+ Use option 1. Catalogs are `.po` files in the repository, compiled to `.mo` for runtime. Babel is a
27
+ runtime dependency, used for loading `.mo` files and plural rules, and the CLI builds on Babel's
28
+ extraction and compilation.
29
+
30
+ ## Consequences
31
+
32
+ - Translators can use the tools they already have (C-01).
33
+ - Plural rules come from CLDR through each catalog's `Plural-Forms` header (TRN-02).
34
+ - Babel becomes a required dependency. It is mature and widely used, so the risk is low.
35
+ - JSON catalogs can still be added later behind the same catalog store interface, as the SRS notes.
36
+
37
+ ## Related requirements
38
+
39
+ C-01, C-04, CAT-01, CAT-02, TRN-02, CLI-01, CLI-02
@@ -0,0 +1,53 @@
1
+ # ADR-0003: Hold the request locale in a context variable with a per-request holder
2
+
3
+ - Status: Accepted
4
+ - Accepted: 2026-09-28
5
+ - Date: 2026-09-26
6
+
7
+ ## Context
8
+
9
+ Code deep inside a request (a service function, a Pydantic serializer, a background task) needs to know
10
+ the request's locale without it being passed through every call. FastAPI runs async code on the event
11
+ loop and sync code in a thread pool, and many requests run at the same time.
12
+
13
+ Experiments with FastAPI 0.141.1 showed:
14
+
15
+ - A value set in a `ContextVar` by pure ASGI middleware is visible in sync dependencies, the endpoint,
16
+ exception handlers and background tasks.
17
+ - A value set with `ContextVar.set()` inside a dependency or a sync endpoint is **not** visible to
18
+ exception handlers or response serialization, because those run in a different context copy.
19
+
20
+ ## Options considered
21
+
22
+ 1. **Thread-local or global state.** Wrong under asyncio: concurrent requests on one thread overwrite
23
+ each other. Rejected.
24
+ 2. **`request.state` only.** Safe, but code without the `Request` object cannot reach it, which rules out
25
+ lazy text and plain `gettext()` calls.
26
+ 3. **`ContextVar` holding the locale value.** Safe and reachable everywhere, but changes made in a
27
+ dependency are lost, as the experiment shows.
28
+ 4. **`ContextVar` holding a small mutable per-request object (`RequestLocale`).** The middleware creates
29
+ one object per request and sets it once. Later changes modify the object, so every context copy that
30
+ refers to it sees them.
31
+
32
+ ## Decision
33
+
34
+ Use option 4. The middleware creates a `RequestLocale`, sets it in a module-level `ContextVar` and resets
35
+ the variable with its token when the request ends. `set_locale()` changes the object in place.
36
+ `use_locale()` sets a new, separate object for the length of a block and restores the previous one with its
37
+ token.
38
+
39
+ The `RequestLocale` is also stored in the ASGI scope so code that has the `Request` can reach it
40
+ explicitly.
41
+
42
+ ## Consequences
43
+
44
+ - Correct under asyncio and in the thread pool (C-02, NFR-07).
45
+ - Changes made by the user's dependency reach the 422 handler and the `Content-Language` header
46
+ (see ADR-0004).
47
+ - Tasks started with `asyncio.create_task` inside a request share the object. A change by one task is seen
48
+ by the others. This is the intended behaviour for one request and is documented.
49
+ - Several applications in one process each create their own holders, so they do not interfere (DI-04).
50
+
51
+ ## Related requirements
52
+
53
+ C-02, LOC-01, LOC-13, LOC-14, LOC-15, TRN-05, TRN-06, DI-04, NFR-07
@@ -0,0 +1,51 @@
1
+ # ADR-0004: Resolve the locale in middleware and let dependencies refine it
2
+
3
+ - Status: Accepted
4
+ - Accepted: 2026-09-28
5
+ - Date: 2026-09-26
6
+
7
+ ## Context
8
+
9
+ FastAPI developers expect dependency injection, so resolving the locale in a dependency looks natural.
10
+ But a locale must also be available where no route dependency runs: the 422 validation handler, HTTP
11
+ exception handlers, and the response headers.
12
+
13
+ Many applications also store a preferred language on the user. The user is usually loaded in an auth
14
+ dependency, which runs after any middleware. This was open issue OI-05 in the SRS.
15
+
16
+ Experiments with FastAPI 0.141.1 showed:
17
+
18
+ - A locale set only in an app-level dependency (as fastapi-i18n does) is missing in the 422 handler.
19
+ - FastAPI runs a route's sub-dependencies before it validates the endpoint's own body and parameters. A
20
+ `current_user` dependency that changes the per-request holder (ADR-0003) is therefore seen by the 422
21
+ handler when the body is invalid, and by the middleware when it writes `Content-Language`.
22
+
23
+ ## Options considered
24
+
25
+ 1. **Dependency only.** Idiomatic, but 422 errors and exception handlers would ignore the locale. Rejected.
26
+ 2. **Middleware only.** Always available, but middleware cannot use the user that a dependency loads.
27
+ 3. **Middleware resolves, dependencies may refine.** The middleware resolves from the request (query,
28
+ cookie, header). An application dependency may call `set_locale()` to replace it for the rest of the
29
+ request.
30
+ 4. **The 422 handler calls an async user hook** to look up the user's language. Duplicates the
31
+ application's auth logic inside an error handler, and runs it twice per request.
32
+
33
+ ## Decision
34
+
35
+ Use option 3. `LocaleMiddleware`, a pure ASGI middleware (not `BaseHTTPMiddleware`), resolves the locale
36
+ before routing. Dependencies read it through `LocaleDep` and `TranslatorDep` and can change it with
37
+ `set_locale()`.
38
+
39
+ ## Consequences
40
+
41
+ - Localized 422 errors work with no extra setup, and they follow the user's language when the
42
+ application sets it in a dependency.
43
+ - Known limit: if the user dependency's own parameters fail validation (for example a malformed
44
+ `Authorization` header parameter), that dependency does not run and the response uses the locale from the
45
+ request. The user guide will state this.
46
+ - WebSocket connections get the same resolution (LOC-12).
47
+ - Pure ASGI middleware avoids the known context and streaming problems of `BaseHTTPMiddleware`.
48
+
49
+ ## Related requirements
50
+
51
+ LOC-01, LOC-10, LOC-11, LOC-12, LOC-13, ERR-01, DI-02, DI-03
@@ -0,0 +1,56 @@
1
+ # ADR-0005: Lazy text as a dedicated Pydantic-aware type
2
+
3
+ - Status: Accepted
4
+ - Accepted: 2026-09-28
5
+ - Date: 2026-09-26
6
+
7
+ ## Context
8
+
9
+ Text defined at import time (default field values, shared error messages) must be translated later, in
10
+ the locale of the request that sends it. Django calls this `gettext_lazy`.
11
+
12
+ Experiments with FastAPI 0.141.1 and Pydantic 2.13.5 (see [Existing libraries](../research/existing-libraries.md),
13
+ section 6) showed that Babel's `LazyProxy`, which starlette-babel and starlette-i18n use, fails as
14
+ `HTTPException.detail`, in a response model, inside a returned dict, and in OpenAPI generation.
15
+
16
+ ## Options considered
17
+
18
+ 1. **Babel `LazyProxy`.** Fails in every FastAPI path tested. Rejected.
19
+ 2. **A `str` subclass** holding the msgid and overriding `__str__`. JSON encoders read the underlying
20
+ string, so `json.dumps` writes the msgid, untranslated. Rejected.
21
+ 3. **A dedicated class with Pydantic core schema hooks.** Serializes through a plain serializer that calls
22
+ `str()`, which translates in the active locale. Tested: works as a model field typed with the class, and
23
+ shows in OpenAPI as `{"type": "string"}`.
24
+ 4. **No lazy text**; translate eagerly everywhere. Simple, but import-time text cannot be localized.
25
+
26
+ ## Decision
27
+
28
+ Use option 3, a final class `LazyText`:
29
+
30
+ - Created by `gettext_lazy`, `ngettext_lazy`, `pgettext_lazy` and `npgettext_lazy`.
31
+ - `str()` and `format()` translate in the active locale.
32
+ - Pydantic validation accepts `LazyText` or `str`; JSON serialization always produces a string. Python-mode
33
+ `model_dump()` keeps the `LazyText`, so it can be rendered later.
34
+ - JSON schema is `{"type": "string"}`.
35
+ - Registered in `fastapi.encoders.ENCODERS_BY_TYPE` so `jsonable_encoder` renders it.
36
+ - Carries a `__pydantic_serializer__`, so Pydantic also renders it where the declared type is `Any` or
37
+ `object`. FastAPI 0.141 serializes a route annotated `-> dict[str, object]` with Pydantic directly,
38
+ without `jsonable_encoder`, which an integration test found.
39
+ - The library's HTTP exception handler renders `detail` through `jsonable_encoder`, because FastAPI's
40
+ default handler passes `detail` straight to `json.dumps`.
41
+ - Equal when message, plural, context, domain and parameters are equal; hash on message, plural, context
42
+ and domain. Not equal to a plain `str`, because that comparison would depend on the active locale.
43
+ - Supports pickling, so it can be passed to task queues.
44
+
45
+ ## Consequences
46
+
47
+ - Works in response models, `HTTPException.detail` and plain dict responses (LZY-01 to LZY-05).
48
+ - Model fields that hold lazy text must be typed `LazyText`, not `str`. Pydantic's error makes the mistake
49
+ obvious, and the user guide covers it.
50
+ - `ENCODERS_BY_TYPE` is not documented FastAPI API. A test fails if FastAPI changes it, so the break is
51
+ caught in CI before a release.
52
+ - Lazy text in OpenAPI metadata is handled separately (ADR-0009).
53
+
54
+ ## Related requirements
55
+
56
+ LZY-01 to LZY-06, ERR-09
@@ -0,0 +1,51 @@
1
+ # ADR-0006: Key validation messages on the Pydantic error type
2
+
3
+ - Status: Accepted
4
+ - Accepted: 2026-09-28
5
+ - Date: 2026-09-26
6
+
7
+ ## Context
8
+
9
+ FastAPI's 422 responses carry Pydantic's English `msg`. pydantic-core 2.46.5 defines 104 error types; 47
10
+ have values in `ctx` such as `gt` or `min_length`. Some English messages build plurals inside `ctx`
11
+ (`string_too_short` uses `{expected_plural}`), which is wrong for most other languages.
12
+
13
+ pydantic-i18n, the only existing package in this area, matches the rendered English `msg` with a regular
14
+ expression. That breaks silently when Pydantic changes its wording.
15
+
16
+ ## Options considered
17
+
18
+ 1. **Match the English `msg` text.** Fragile across Pydantic releases. Rejected.
19
+ 2. **Use Pydantic's own templates as msgids.** Ties catalogs to Pydantic's wording, which changes, and
20
+ keeps the English plural logic.
21
+ 3. **Library-owned templates keyed on `type`.** The library keeps one English template per error type,
22
+ with named placeholders that match `ctx` keys, and uses the type as the gettext message context.
23
+
24
+ ## Decision
25
+
26
+ Use option 3.
27
+
28
+ - Templates live in the library's own gettext domain, `fastapi_locale`, with `msgctxt` set to the error
29
+ type.
30
+ - Types whose message depends on a number have a plural template and name the `ctx` key that picks the
31
+ form (for example `min_length`). English-only `ctx` keys such as `expected_plural` are ignored.
32
+ - The built-in catalog directory is loaded first. An application overrides any message by shipping its own
33
+ `fastapi_locale` catalog in its catalog directories, because later directories win (CAT-04).
34
+ - An error type with no template keeps Pydantic's `msg`.
35
+ - For `value_error` and `assertion_error`, only the fixed prefix is translated. The rest of the message is
36
+ what the application raised, which it can translate with `gettext()` because validation runs inside the
37
+ request.
38
+
39
+ ## Consequences
40
+
41
+ - Catalogs no longer depend on Pydantic's wording (A-02).
42
+ - Plurals follow the target language's rules (ERR-04).
43
+ - The library must follow new error types in pydantic-core. A test lists all error types of the installed
44
+ pydantic-core and fails when one has no template (NFR-09). It uses `list_all_errors`, which is not public
45
+ Pydantic API, so it runs only in the test suite.
46
+ - Known limit: some `ctx` values are already English, for example `literal_error` renders `expected` as
47
+ `'a' or 'b'`. Those values are passed through as they are.
48
+
49
+ ## Related requirements
50
+
51
+ ERR-01 to ERR-08, NFR-09
@@ -0,0 +1,42 @@
1
+ # ADR-0007: Named placeholder formatting instead of str.format
2
+
3
+ - Status: Accepted
4
+ - Accepted: 2026-09-28
5
+ - Date: 2026-09-26
6
+
7
+ ## Context
8
+
9
+ Translated messages contain values such as a name or a count. Translators must be able to move them,
10
+ because word order differs between languages. Catalogs are often edited by outside contributors, so a
11
+ translated string must not be able to run code or read arbitrary data.
12
+
13
+ ## Options considered
14
+
15
+ 1. **`str.format(**params)`.** A translated string like `{user.__class__}` or `{items[0]}` can read
16
+ attributes and items of the arguments. Rejected for security (NFR-05).
17
+ 2. **`%` formatting (`%(name)s`).** Safe from attribute access, but a translation with a missing key raises
18
+ `KeyError`, and the `%` syntax is easy to break when editing.
19
+ 3. **Own substitution of `{name}` only.** A small regular expression replaces `{identifier}` with the
20
+ string form of the named value. `{{` and `}}` produce literal braces. Anything else is left as written.
21
+
22
+ ## Decision
23
+
24
+ Use option 3. The same syntax is used for application messages and validation error templates, and it
25
+ matches the `{gt}` style of Pydantic's `ctx` keys.
26
+
27
+ If a translation names a placeholder that has no value, the placeholder is left as written and a warning
28
+ is logged once per message and locale. The request never fails because of it (NFR-06).
29
+
30
+ `fastapi-locale check` reports translations whose placeholders differ from the msgid, so these mistakes
31
+ are caught in CI before they reach production.
32
+
33
+ ## Consequences
34
+
35
+ - Translators can reorder values freely (TRN-03).
36
+ - Translated strings cannot read attributes or items.
37
+ - Format specifications such as `{price:.2f}` are not supported. Numbers that need locale formatting will
38
+ use the formatting helpers planned in FMT-02.
39
+
40
+ ## Related requirements
41
+
42
+ TRN-03, NFR-05, NFR-06, CLI-04
@@ -0,0 +1,37 @@
1
+ # ADR-0008: Load catalogs when the application is set up
2
+
3
+ - Status: Accepted
4
+ - Accepted: 2026-09-28
5
+ - Date: 2026-09-26
6
+
7
+ ## Context
8
+
9
+ Catalogs must be loaded before the first request, and no file access may happen while a request is being
10
+ handled (C-03). The first draft of the SRS said "at startup (lifespan)".
11
+
12
+ ## Options considered
13
+
14
+ 1. **In the ASGI lifespan startup event.** Fits the idea of startup, but FastAPI applications set their own
15
+ `lifespan`, and combining two is awkward. Starlette's `TestClient` also skips lifespan unless it is used
16
+ as a context manager, so tests would see unloaded catalogs.
17
+ 2. **On the first request.** Adds latency to that request and does file access during request handling.
18
+ Rejected.
19
+ 3. **When `Localization` is created.** Catalogs load when the application module builds its
20
+ `Localization(config)`, before the server accepts connections.
21
+
22
+ ## Decision
23
+
24
+ Use option 3. Loading is fast (it reads compiled `.mo` files), and a failure raises `CatalogLoadError` at
25
+ import time, so a broken deployment never starts. The SRS requirement CAT-03 is worded to match: catalogs
26
+ are loaded during application setup, before the first request.
27
+
28
+ ## Consequences
29
+
30
+ - Works the same with or without lifespan, in tests and in production.
31
+ - Errors appear at import, which is where developers look first.
32
+ - Importing the application module reads files. This is the same as most configuration loading and is
33
+ documented.
34
+
35
+ ## Related requirements
36
+
37
+ C-03, CAT-03, CAT-05