Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions examples/fastapi-eager/_gt/es.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"8042e0a3d395c1fb": "Hola, mundo!",
"9b323e35e1a80c51": "Hola, {name}!"
}
4 changes: 4 additions & 0 deletions examples/fastapi-eager/_gt/fr.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"8042e0a3d395c1fb": "Bonjour, le monde!",
"9b323e35e1a80c51": "Bonjour, {name}!"
}
37 changes: 18 additions & 19 deletions examples/fastapi-eager/app.py
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)
Comment on lines +19 to +20
Copy link

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:
    with open(BASE_DIR / "gt.config.json") as f:
        config = json.load(f)
except FileNotFoundError as exc:
    raise FileNotFoundError(
        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
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:

```python
try:
    with open(BASE_DIR / "gt.config.json") as f:
        config = json.load(f)
except FileNotFoundError as exc:
    raise FileNotFoundError(
        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.



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"
Copy link

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:

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
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:

```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,
)
Expand Down
4 changes: 4 additions & 0 deletions examples/fastapi-eager/gt.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"defaultLocale": "en",
"locales": ["es", "fr"]
}
4 changes: 4 additions & 0 deletions examples/fastapi-lazy/_gt/es.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"8042e0a3d395c1fb": "Hola, mundo!",
"9b323e35e1a80c51": "Hola, {name}!"
}
4 changes: 4 additions & 0 deletions examples/fastapi-lazy/_gt/fr.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"8042e0a3d395c1fb": "Bonjour, le monde!",
"9b323e35e1a80c51": "Bonjour, {name}!"
}
34 changes: 18 additions & 16 deletions examples/fastapi-lazy/app.py
Original file line number Diff line number Diff line change
@@ -1,36 +1,38 @@
"""FastAPI example with lazy translation loading.

Translations are loaded on first request per locale, not at startup.
Translations are stored in _gt/<locale>.json and loaded on first request per locale.
Configuration is read from gt.config.json.
Run: uv run uvicorn app:app --port 8001
"""

import json
from pathlib import Path

from fastapi import Depends, FastAPI, Request
from gt_fastapi import initialize_gt, t

app = FastAPI(title="FastAPI Lazy Example")

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)


async def load_translations(locale: str) -> dict[str, str]:
"""Simulate loading translations from a remote source."""
print(f"[lazy] Loading translations for '{locale}'")
return TRANSLATIONS.get(locale, {})
"""Load translations from _gt/<locale>.json."""
path = GT_DIR / f"{locale}.json"
if path.exists():
with open(path) as f:
return json.load(f)
return {}


manager = 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=False,
)
Expand Down
4 changes: 4 additions & 0 deletions examples/fastapi-lazy/gt.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"defaultLocale": "en",
"locales": ["es", "fr"]
}
4 changes: 4 additions & 0 deletions examples/flask-eager/_gt/es.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"8042e0a3d395c1fb": "Hola, mundo!",
"9b323e35e1a80c51": "Hola, {name}!"
}
4 changes: 4 additions & 0 deletions examples/flask-eager/_gt/fr.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"8042e0a3d395c1fb": "Bonjour, le monde!",
"9b323e35e1a80c51": "Bonjour, {name}!"
}
34 changes: 18 additions & 16 deletions examples/flask-eager/app.py
Original file line number Diff line number Diff line change
@@ -1,36 +1,38 @@
"""Flask 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 python app.py (serves on port 5050)
"""

import json
from pathlib import Path

from flask import Flask
from gt_flask import initialize_gt, t

app = Flask(__name__)

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"
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,
)
Expand Down
4 changes: 4 additions & 0 deletions examples/flask-eager/gt.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"defaultLocale": "en",
"locales": ["es", "fr"]
}
4 changes: 4 additions & 0 deletions examples/flask-lazy/_gt/es.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"8042e0a3d395c1fb": "Hola, mundo!",
"9b323e35e1a80c51": "Hola, {name}!"
}
4 changes: 4 additions & 0 deletions examples/flask-lazy/_gt/fr.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"8042e0a3d395c1fb": "Bonjour, le monde!",
"9b323e35e1a80c51": "Bonjour, {name}!"
}
33 changes: 17 additions & 16 deletions examples/flask-lazy/app.py
Original file line number Diff line number Diff line change
@@ -1,38 +1,39 @@
"""Flask example with lazy translation loading.

Translations are loaded on first request per locale, not at startup.
Translations are stored in _gt/<locale>.json and loaded on first request per locale.
Configuration is read from gt.config.json.
Run: uv run python app.py (serves on port 5051)
"""

import asyncio
import json
from pathlib import Path

from flask import Flask
from gt_flask import initialize_gt, t

app = Flask(__name__)

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]:
"""Simulate loading translations from a remote source."""
print(f"[lazy] Loading translations for '{locale}'")
return TRANSLATIONS.get(locale, {})
"""Load translations from _gt/<locale>.json."""
path = GT_DIR / f"{locale}.json"
if path.exists():
with open(path) as f:
return json.load(f)
return {}


manager = 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=False,
)
Expand Down
4 changes: 4 additions & 0 deletions examples/flask-lazy/gt.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"defaultLocale": "en",
"locales": ["es", "fr"]
}