Skip to content

chore: use gt.config.json and _gt/<locale>.json for example translations - #20

Open
moss-bryophyta wants to merge 5 commits into
generaltranslation:mainfrom
moss-bryophyta:moss/update-examples
Open

chore: use gt.config.json and _gt/<locale>.json for example translations#20
moss-bryophyta wants to merge 5 commits into
generaltranslation:mainfrom
moss-bryophyta:moss/update-examples

Conversation

@moss-bryophyta

@moss-bryophytamoss-bryophyta commented Mar 8, 2026

Copy link
Copy Markdown
Contributor

Refactors all four examples (fastapi-eager, fastapi-lazy, flask-eager, flask-lazy) to:

  • Read config from gt.config.jsondefaultLocale and locales are now defined in a config file rather than hardcoded in the app
  • Store translations in _gt/<locale>.json — each locale gets its own JSON file with hash-keyed translations
  • Use a load_translations function that reads from the _gt/ directory

The custom load_translations function is still required for now but will be removed in a future release.

Tests are unchanged — they create their own self-contained app fixtures.

Greptile Summary

This PR refactors all four example applications (fastapi-eager, fastapi-lazy, flask-eager, flask-lazy) to externalize configuration and translations: locale settings move from hardcoded values to gt.config.json, and translation strings move from inline dictionaries to per-locale _gt/<locale>.json files. The changes align the examples with the intended file-based workflow that users of the library would follow in practice.

Key changes across all four apps:

  • Config externalisation: defaultLocale and locales are now read from gt.config.json via a module-level json.load() call.
  • Translation file loading: The inline TRANSLATIONS dict is replaced by a load_translations(locale) function that reads _gt/<locale>.json from disk, returning {} for unknown locales.
  • Debug print() statements removed: The [eager]/[lazy] loading prints that were in the old load_translations functions are gone — a clean improvement.
  • "en" omitted from locales in config: The gt.config.json files only list ["es", "fr"]. This is correct because I18nManager always adds default_locale to its internal locale set, so "en" is still supported at runtime.
  • Tests are unaffected: All test fixtures create their own self-contained apps and are not impacted by this change.

Confidence Score: 4/5

  • Safe to merge — changes are example-only refactoring with no logic regressions; two minor style concerns worth addressing.
  • The functional change is correct and well-scoped to the examples directory. Tests are unaffected. The two flagged issues (unsanitized locale in file-path construction and bare module-level open) are style concerns that could be confusing for developers copying the example pattern into production code, but they do not cause runtime failures in the examples themselves.
  • The four app.py files all share the same two patterns worth fixing before this code is promoted as a copy-paste reference for users.

Important Files Changed

FilenameOverview
examples/fastapi-eager/app.pyRefactored to read config from gt.config.json and load translations from _gt/.json. Two style concerns: unsanitized locale in file path construction and unguarded module-level open() with no error handling.
examples/fastapi-lazy/app.pySame refactoring pattern as fastapi-eager; shares the same unsanitized locale path and bare module-level open() concerns.
examples/flask-eager/app.pySame refactoring pattern; same unsanitized locale path and bare module-level open() concerns as the FastAPI examples.
examples/flask-lazy/app.pySame refactoring pattern; same unsanitized locale path and bare module-level open() concerns as the other examples.
examples/fastapi-eager/gt.config.jsonNew config file externalising defaultLocale and locales. Note that "en" is intentionally omitted from locales (it's the default/source locale); I18nManager adds it internally.
examples/fastapi-eager/_gt/es.jsonNew hash-keyed translation file for Spanish; content matches prior inline dictionary exactly.
examples/fastapi-eager/_gt/fr.jsonNew hash-keyed translation file for French; content matches prior inline dictionary exactly.

Sequence Diagram

sequenceDiagram
participant App as app.py (module load)
participant CFG as gt.config.json
participant GT as initialize_gt()
participant MGR as I18nManager
participant LDR as load_translations(locale)
participant FS as _gt/<locale>.json
App->>CFG: open & json.load()
CFG-->>App: {defaultLocale, locales}
App->>GT: initialize_gt(default_locale, locales, load_translations, eager_loading)
GT->>MGR: I18nManager(default_locale, locales=[en,es,fr])
Note over MGR: Internally adds default_locale to locales set
alt eager_loading=True
MGR->>LDR: load_translations("en")
LDR->>FS: open _gt/en.json (not found)
LDR-->>MGR: {}
MGR->>LDR: load_translations("es")
LDR->>FS: open _gt/es.json
FS-->>LDR: {hash: "Hola, mundo!", ...}
LDR-->>MGR: translations dict
MGR->>LDR: load_translations("fr")
LDR->>FS: open _gt/fr.json
FS-->>LDR: {hash: "Bonjour, le monde!", ...}
LDR-->>MGR: translations dict
end
Note over App,MGR: App ready — translations cached in manager
Loading

Last reviewed commit: 09d2f1f

Greptile also left 2 inline comments on this PR.

print(f"[eager] Loading translations for '{locale}'")
return TRANSLATIONS.get(locale, {})
"""Load translations from _gt/<locale>.json."""
path = GT_DIR / f"{locale}.json"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

defload_translations(locale: str) ->dict[str, str]:
"""Load translations from _gt/<locale>.json."""importreifnotre.fullmatch(r"[a-zA-Z0-9_-]+", locale):
return {}
path=GT_DIR/f"{locale}.json"ifpath.exists():
withopen(path) asf:
returnjson.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
Prompt To Fix With AI
This 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:
```pythondefload_translations(locale: str) -> dict[str, str]:
"""Load translations from _gt/<locale>.json."""import re
ifnot re.fullmatch(r"[a-zA-Z0-9_-]+", locale):
return {}
path =GT_DIR/f"{locale}.json"if path.exists():
withopen(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.

Comment threadexamples/fastapi-eager/app.py Outdated
Comment on lines +19 to +20
with open(BASE_DIR / "gt.config.json") as f:
config = json.load(f)

Copy link
Copy Markdown

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 handling

gt.config.json is 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 unhandled FileNotFoundError that immediately aborts the import with an opaque traceback — no indication of which file is missing or how to fix it.

Notably, initialize_gt already accepts a config_path parameter and delegates to load_gt_config(), which includes proper error handling (returns an empty config when the file is absent, raises a descriptive ValueError for malformed JSON). Since the apps manually pass the resolved values anyway, the manual load is redundant; using config_path directly would be cleaner:

initialize_gt(
app,
config_path=str(BASE_DIR/"gt.config.json"),
load_translations=load_translations,
eager_loading=True,
)

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/except with a descriptive message:

try:
withopen(BASE_DIR/"gt.config.json") asf:
config=json.load(f)
exceptFileNotFoundErrorasexc:
raiseFileNotFoundError(
f"gt.config.json not found at {BASE_DIR/'gt.config.json'}. ""Please create it or run the app from the correct directory."
) fromexc

The same pattern appears in all four examples:

  • examples/fastapi-lazy/app.py:19-20
  • examples/flask-eager/app.py:19-20
  • examples/flask-lazy/app.py:20-21
Prompt To Fix With AI
This is a comment left during a code review.
Path: examples/fastapi-eager/app.py
Line: 19-20
Comment:
**Bare `open()` at module level with no error handling**`gt.config.json` is 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 unhandled `FileNotFoundError` that immediately aborts the import with an opaque traceback — no indication of which file is missing or how to fix it.
Notably, `initialize_gt` already accepts a `config_path` parameter and delegates to `load_gt_config()`, which includes proper error handling (returns an empty config when the file is absent, raises a descriptive `ValueError` for malformed JSON). Since the apps manually pass the resolved values anyway, the manual load is redundant; using `config_path` directly would be cleaner:
```python
initialize_gt(
app,
config_path=str(BASE_DIR/"gt.config.json"),
load_translations=load_translations,
eager_loading=True,
)
```
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/except` with a descriptive message:
```pythontry:
withopen(BASE_DIR/"gt.config.json") as f:
config = json.load(f)
exceptFileNotFoundErroras exc:
raiseFileNotFoundError(
f"gt.config.json not found at {BASE_DIR/'gt.config.json'}. ""Please create it or run the app from the correct directory."
) from exc
```
The same pattern appears in all four examples:
-`examples/fastapi-lazy/app.py:19-20`-`examples/flask-eager/app.py:19-20`-`examples/flask-lazy/app.py:20-21`
How can I resolve this? If you propose a fix, please make it concise.

@moss-bryophytamoss-bryophyta changed the title examples: use gt.config.json and _gt/<locale>.json for translationschore: use gt.config.json and _gt/<locale>.json for example translationsMar 8, 2026
Refactor all examples to:
- Read configuration from gt.config.json
- Store translations in _gt/<locale>.json files
- Use a load_translations function that reads from _gt/ directory
The custom load_translations function is still required for now but
will be removed in a future release.
Remove manual gt.config.json parsing — initialize_gt reads it
automatically from the current working directory.
- Remove redundant config params (read from gt.config.json)
- Simplify load_translations helpers
- Fix Flask eager loading bug: use resolved_locales instead of locales param
- Clean up docstrings and imports
- All four examples tested and working
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@moss-bryophyta