-
Notifications
You must be signed in to change notification settings - Fork 1
chore: use gt.config.json and _gt/<locale>.json for example translations #20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| { | ||
| "8042e0a3d395c1fb": "Hola, mundo!", | ||
| "9b323e35e1a80c51": "Hola, {name}!" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| { | ||
| "8042e0a3d395c1fb": "Bonjour, le monde!", | ||
| "9b323e35e1a80c51": "Bonjour, {name}!" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,39 +1,38 @@ | ||
| """FastAPI example with eager translation loading. | ||
|
|
||
| Translations are loaded at startup for all configured locales. | ||
| Translations are stored in _gt/<locale>.json and loaded at startup. | ||
| Configuration is read from gt.config.json. | ||
| Run: uv run uvicorn app:app --port 8000 | ||
| """ | ||
|
|
||
| import json | ||
| from pathlib import Path | ||
|
|
||
| from fastapi import FastAPI | ||
| from gt_fastapi import initialize_gt, t | ||
|
|
||
| app = FastAPI(title="FastAPI Eager Example") | ||
|
|
||
| # Pre-built translation dictionaries keyed by hash. | ||
| # hash_message("Hello, world!") -> "8042e0a3d395c1fb" | ||
| # hash_message("Hello, {name}!") -> "9b323e35e1a80c51" | ||
| TRANSLATIONS: dict[str, dict[str, str]] = { | ||
| "es": { | ||
| "8042e0a3d395c1fb": "Hola, mundo!", | ||
| "9b323e35e1a80c51": "Hola, {name}!", | ||
| }, | ||
| "fr": { | ||
| "8042e0a3d395c1fb": "Bonjour, le monde!", | ||
| "9b323e35e1a80c51": "Bonjour, {name}!", | ||
| }, | ||
| } | ||
| BASE_DIR = Path(__file__).parent | ||
| GT_DIR = BASE_DIR / "_gt" | ||
|
|
||
| with open(BASE_DIR / "gt.config.json") as f: | ||
| config = json.load(f) | ||
|
|
||
|
|
||
| def load_translations(locale: str) -> dict[str, str]: | ||
| """Return translations for a locale from the in-memory dictionary.""" | ||
| print(f"[eager] Loading translations for '{locale}'") | ||
| return TRANSLATIONS.get(locale, {}) | ||
| """Load translations from _gt/<locale>.json.""" | ||
| path = GT_DIR / f"{locale}.json" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unsanitized locale in file path The While the appended def load_translations(locale: str) -> dict[str, str]:
"""Load translations from _gt/<locale>.json."""
import re
if not re.fullmatch(r"[a-zA-Z0-9_-]+", locale):
return {}
path = GT_DIR / f"{locale}.json"
if path.exists():
with open(path) as f:
return json.load(f)
return {}The same pattern appears in all four examples:
Prompt To Fix With AIThis is a comment left during a code review.
Path: examples/fastapi-eager/app.py
Line: 25
Comment:
**Unsanitized locale in file path**
The `locale` string is taken directly from the `Accept-Language` header (after `determine_locale` resolves it) and used to construct a file path without any validation. A crafted header containing traversal sequences such as `../../secrets` would build a path like `_gt/../../secrets.json`, potentially reaching files outside the `_gt/` directory.
While the appended `.json` extension and the `path.exists()` guard reduce practical risk, a defence-in-depth approach would validate that the resolved locale only contains safe characters before using it in path construction:
```python
def load_translations(locale: str) -> dict[str, str]:
"""Load translations from _gt/<locale>.json."""
import re
if not re.fullmatch(r"[a-zA-Z0-9_-]+", locale):
return {}
path = GT_DIR / f"{locale}.json"
if path.exists():
with open(path) as f:
return json.load(f)
return {}
```
The same pattern appears in all four examples:
- `examples/fastapi-lazy/app.py:25`
- `examples/flask-eager/app.py:25`
- `examples/flask-lazy/app.py:25`
How can I resolve this? If you propose a fix, please make it concise. |
||
| if path.exists(): | ||
| with open(path) as f: | ||
| return json.load(f) | ||
| return {} | ||
|
|
||
|
|
||
| initialize_gt( | ||
| app, | ||
| default_locale="en", | ||
| locales=["en", "es", "fr"], | ||
| default_locale=config.get("defaultLocale", "en"), | ||
| locales=config.get("locales"), | ||
| load_translations=load_translations, | ||
| eager_loading=True, | ||
| ) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| { | ||
| "defaultLocale": "en", | ||
| "locales": ["es", "fr"] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| { | ||
| "8042e0a3d395c1fb": "Hola, mundo!", | ||
| "9b323e35e1a80c51": "Hola, {name}!" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| { | ||
| "8042e0a3d395c1fb": "Bonjour, le monde!", | ||
| "9b323e35e1a80c51": "Bonjour, {name}!" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| { | ||
| "defaultLocale": "en", | ||
| "locales": ["es", "fr"] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| { | ||
| "8042e0a3d395c1fb": "Hola, mundo!", | ||
| "9b323e35e1a80c51": "Hola, {name}!" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| { | ||
| "8042e0a3d395c1fb": "Bonjour, le monde!", | ||
| "9b323e35e1a80c51": "Bonjour, {name}!" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| { | ||
| "defaultLocale": "en", | ||
| "locales": ["es", "fr"] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| { | ||
| "8042e0a3d395c1fb": "Hola, mundo!", | ||
| "9b323e35e1a80c51": "Hola, {name}!" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| { | ||
| "8042e0a3d395c1fb": "Bonjour, le monde!", | ||
| "9b323e35e1a80c51": "Bonjour, {name}!" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| { | ||
| "defaultLocale": "en", | ||
| "locales": ["es", "fr"] | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bare
open()at module level with no error handlinggt.config.jsonis opened at module level without any error handling. If the file is missing (e.g., the app is started from a different working directory, or the file is accidentally excluded from a deployment), Python will raise an unhandledFileNotFoundErrorthat immediately aborts the import with an opaque traceback — no indication of which file is missing or how to fix it.Notably,
initialize_gtalready accepts aconfig_pathparameter and delegates toload_gt_config(), which includes proper error handling (returns an empty config when the file is absent, raises a descriptiveValueErrorfor malformed JSON). Since the apps manually pass the resolved values anyway, the manual load is redundant; usingconfig_pathdirectly would be cleaner:If the manual load is intentional (e.g., to access the config values in other parts of the module), at minimum wrap it in a
try/exceptwith a descriptive message:The same pattern appears in all four examples:
examples/fastapi-lazy/app.py:19-20examples/flask-eager/app.py:19-20examples/flask-lazy/app.py:20-21Prompt To Fix With AI