From 09d2f1f66761a939c007adf6eaa70e7b3f7338bd Mon Sep 17 00:00:00 2001 From: moss-bryophyta <261561981+moss-bryophyta@users.noreply.github.com> Date: Sat, 7 Mar 2026 21:19:53 -0800 Subject: [PATCH 1/3] examples: use gt.config.json and _gt/.json for translations Refactor all examples to: - Read configuration from gt.config.json - Store translations in _gt/.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. --- examples/fastapi-eager/_gt/es.json | 4 +++ examples/fastapi-eager/_gt/fr.json | 4 +++ examples/fastapi-eager/app.py | 37 +++++++++++++-------------- examples/fastapi-eager/gt.config.json | 4 +++ examples/fastapi-lazy/_gt/es.json | 4 +++ examples/fastapi-lazy/_gt/fr.json | 4 +++ examples/fastapi-lazy/app.py | 34 ++++++++++++------------ examples/fastapi-lazy/gt.config.json | 4 +++ examples/flask-eager/_gt/es.json | 4 +++ examples/flask-eager/_gt/fr.json | 4 +++ examples/flask-eager/app.py | 34 ++++++++++++------------ examples/flask-eager/gt.config.json | 4 +++ examples/flask-lazy/_gt/es.json | 4 +++ examples/flask-lazy/_gt/fr.json | 4 +++ examples/flask-lazy/app.py | 33 ++++++++++++------------ examples/flask-lazy/gt.config.json | 4 +++ 16 files changed, 119 insertions(+), 67 deletions(-) create mode 100644 examples/fastapi-eager/_gt/es.json create mode 100644 examples/fastapi-eager/_gt/fr.json create mode 100644 examples/fastapi-eager/gt.config.json create mode 100644 examples/fastapi-lazy/_gt/es.json create mode 100644 examples/fastapi-lazy/_gt/fr.json create mode 100644 examples/fastapi-lazy/gt.config.json create mode 100644 examples/flask-eager/_gt/es.json create mode 100644 examples/flask-eager/_gt/fr.json create mode 100644 examples/flask-eager/gt.config.json create mode 100644 examples/flask-lazy/_gt/es.json create mode 100644 examples/flask-lazy/_gt/fr.json create mode 100644 examples/flask-lazy/gt.config.json diff --git a/examples/fastapi-eager/_gt/es.json b/examples/fastapi-eager/_gt/es.json new file mode 100644 index 0000000..84a3870 --- /dev/null +++ b/examples/fastapi-eager/_gt/es.json @@ -0,0 +1,4 @@ +{ + "8042e0a3d395c1fb": "Hola, mundo!", + "9b323e35e1a80c51": "Hola, {name}!" +} diff --git a/examples/fastapi-eager/_gt/fr.json b/examples/fastapi-eager/_gt/fr.json new file mode 100644 index 0000000..fba7e6d --- /dev/null +++ b/examples/fastapi-eager/_gt/fr.json @@ -0,0 +1,4 @@ +{ + "8042e0a3d395c1fb": "Bonjour, le monde!", + "9b323e35e1a80c51": "Bonjour, {name}!" +} diff --git a/examples/fastapi-eager/app.py b/examples/fastapi-eager/app.py index 8a1fae5..b636079 100644 --- a/examples/fastapi-eager/app.py +++ b/examples/fastapi-eager/app.py @@ -1,39 +1,38 @@ """FastAPI example with eager translation loading. -Translations are loaded at startup for all configured locales. +Translations are stored in _gt/.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/.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, ) diff --git a/examples/fastapi-eager/gt.config.json b/examples/fastapi-eager/gt.config.json new file mode 100644 index 0000000..9940de2 --- /dev/null +++ b/examples/fastapi-eager/gt.config.json @@ -0,0 +1,4 @@ +{ + "defaultLocale": "en", + "locales": ["es", "fr"] +} diff --git a/examples/fastapi-lazy/_gt/es.json b/examples/fastapi-lazy/_gt/es.json new file mode 100644 index 0000000..84a3870 --- /dev/null +++ b/examples/fastapi-lazy/_gt/es.json @@ -0,0 +1,4 @@ +{ + "8042e0a3d395c1fb": "Hola, mundo!", + "9b323e35e1a80c51": "Hola, {name}!" +} diff --git a/examples/fastapi-lazy/_gt/fr.json b/examples/fastapi-lazy/_gt/fr.json new file mode 100644 index 0000000..fba7e6d --- /dev/null +++ b/examples/fastapi-lazy/_gt/fr.json @@ -0,0 +1,4 @@ +{ + "8042e0a3d395c1fb": "Bonjour, le monde!", + "9b323e35e1a80c51": "Bonjour, {name}!" +} diff --git a/examples/fastapi-lazy/app.py b/examples/fastapi-lazy/app.py index 64d91c3..0f95651 100644 --- a/examples/fastapi-lazy/app.py +++ b/examples/fastapi-lazy/app.py @@ -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/.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/.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, ) diff --git a/examples/fastapi-lazy/gt.config.json b/examples/fastapi-lazy/gt.config.json new file mode 100644 index 0000000..9940de2 --- /dev/null +++ b/examples/fastapi-lazy/gt.config.json @@ -0,0 +1,4 @@ +{ + "defaultLocale": "en", + "locales": ["es", "fr"] +} diff --git a/examples/flask-eager/_gt/es.json b/examples/flask-eager/_gt/es.json new file mode 100644 index 0000000..84a3870 --- /dev/null +++ b/examples/flask-eager/_gt/es.json @@ -0,0 +1,4 @@ +{ + "8042e0a3d395c1fb": "Hola, mundo!", + "9b323e35e1a80c51": "Hola, {name}!" +} diff --git a/examples/flask-eager/_gt/fr.json b/examples/flask-eager/_gt/fr.json new file mode 100644 index 0000000..fba7e6d --- /dev/null +++ b/examples/flask-eager/_gt/fr.json @@ -0,0 +1,4 @@ +{ + "8042e0a3d395c1fb": "Bonjour, le monde!", + "9b323e35e1a80c51": "Bonjour, {name}!" +} diff --git a/examples/flask-eager/app.py b/examples/flask-eager/app.py index bfe8948..4853e27 100644 --- a/examples/flask-eager/app.py +++ b/examples/flask-eager/app.py @@ -1,36 +1,38 @@ """Flask example with eager translation loading. -Translations are loaded at startup for all configured locales. +Translations are stored in _gt/.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/.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, ) diff --git a/examples/flask-eager/gt.config.json b/examples/flask-eager/gt.config.json new file mode 100644 index 0000000..9940de2 --- /dev/null +++ b/examples/flask-eager/gt.config.json @@ -0,0 +1,4 @@ +{ + "defaultLocale": "en", + "locales": ["es", "fr"] +} diff --git a/examples/flask-lazy/_gt/es.json b/examples/flask-lazy/_gt/es.json new file mode 100644 index 0000000..84a3870 --- /dev/null +++ b/examples/flask-lazy/_gt/es.json @@ -0,0 +1,4 @@ +{ + "8042e0a3d395c1fb": "Hola, mundo!", + "9b323e35e1a80c51": "Hola, {name}!" +} diff --git a/examples/flask-lazy/_gt/fr.json b/examples/flask-lazy/_gt/fr.json new file mode 100644 index 0000000..fba7e6d --- /dev/null +++ b/examples/flask-lazy/_gt/fr.json @@ -0,0 +1,4 @@ +{ + "8042e0a3d395c1fb": "Bonjour, le monde!", + "9b323e35e1a80c51": "Bonjour, {name}!" +} diff --git a/examples/flask-lazy/app.py b/examples/flask-lazy/app.py index 4f87f9d..eae9192 100644 --- a/examples/flask-lazy/app.py +++ b/examples/flask-lazy/app.py @@ -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/.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/.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, ) diff --git a/examples/flask-lazy/gt.config.json b/examples/flask-lazy/gt.config.json new file mode 100644 index 0000000..9940de2 --- /dev/null +++ b/examples/flask-lazy/gt.config.json @@ -0,0 +1,4 @@ +{ + "defaultLocale": "en", + "locales": ["es", "fr"] +} From 877ec87ce56706c6f85ccf50adbd0fdade1bb11e Mon Sep 17 00:00:00 2001 From: moss-bryophyta <261561981+moss-bryophyta@users.noreply.github.com> Date: Sat, 7 Mar 2026 22:05:41 -0800 Subject: [PATCH 2/3] add guides for declare_static and custom get_locale --- guides/custom-get-locale.md | 133 ++++++++++++++++++++++++++++++++++++ guides/declare-static.md | 87 +++++++++++++++++++++++ 2 files changed, 220 insertions(+) create mode 100644 guides/custom-get-locale.md create mode 100644 guides/declare-static.md diff --git a/guides/custom-get-locale.md b/guides/custom-get-locale.md new file mode 100644 index 0000000..2e6129d --- /dev/null +++ b/guides/custom-get-locale.md @@ -0,0 +1,133 @@ +# Custom locale detection with `get_locale` + +## Overview + +Both `gt_fastapi` and `gt_flask` accept a `get_locale` callback in `initialize_gt()` that controls how the user's locale is determined on each request. If you don't provide one, the default behavior parses the `Accept-Language` header. + +## Default behavior + +When no `get_locale` is provided, GT automatically: + +1. Reads the `Accept-Language` HTTP header from the incoming request (e.g. `en-US,en;q=0.9,es;q=0.8`) +2. Parses it into a list of locales sorted by quality value +3. Calls `determine_locale()` to find the best match against your configured `locales` +4. Falls back to `default_locale` if no match is found + +This means out of the box, users get content in whatever language their browser requests — as long as you have translations for it. + +## Providing a custom `get_locale` + +Pass a callable that takes the request object and returns a locale string: + +### FastAPI + +```python +from fastapi import FastAPI, Request +from gt_fastapi import initialize_gt + +app = FastAPI() + +def get_locale(request: Request) -> str: + """Detect locale from a query parameter, cookie, or header.""" + # 1. Check query parameter + locale = request.query_params.get("lang") + if locale: + return locale + + # 2. Check cookie + locale = request.cookies.get("locale") + if locale: + return locale + + # 3. Fall back to default + return "en" + +initialize_gt( + app, + default_locale="en", + locales=["es", "fr"], + get_locale=get_locale, +) +``` + +### Flask + +```python +from flask import Flask, request +from gt_flask import initialize_gt + +app = Flask(__name__) + +def get_locale(req) -> str: + """Detect locale from a query parameter, cookie, or header.""" + # 1. Check query parameter + locale = req.args.get("lang") + if locale: + return locale + + # 2. Check cookie + locale = req.cookies.get("locale") + if locale: + return locale + + # 3. Fall back to default + return "en" + +initialize_gt( + app, + default_locale="en", + locales=["es", "fr"], + get_locale=get_locale, +) +``` + +## How it's used internally + +When a request comes in: + +- **FastAPI**: The `gt_middleware` runs on every request. If `get_locale` is provided, it calls `get_locale(request)`. Otherwise it parses `Accept-Language`. +- **Flask**: A `before_request` hook runs on every request with the same logic. + +The resolved locale is then set on the `I18nManager`, which `t()` reads from when translating strings. + +## Common patterns + +### URL path prefix + +```python +def get_locale(request) -> str: + """Extract locale from URL path like /es/about or /fr/home.""" + parts = request.url.path.strip("/").split("/") + if parts and parts[0] in ("es", "fr", "de"): + return parts[0] + return "en" +``` + +### User profile / database + +```python +def get_locale(request) -> str: + """Look up locale from authenticated user's profile.""" + user = get_current_user(request) # your auth logic + if user and user.preferred_locale: + return user.preferred_locale + return "en" +``` + +### Subdomain + +```python +def get_locale(request) -> str: + """Detect locale from subdomain like es.example.com.""" + host = request.headers.get("host", "") + subdomain = host.split(".")[0] + if subdomain in ("es", "fr", "de"): + return subdomain + return "en" +``` + +## Notes + +- Your `get_locale` function should always return a valid locale string +- If it returns a locale you don't have translations for, `t()` will fall back to the original (default locale) content +- The function receives the raw request object — `Request` for FastAPI, `flask.request` for Flask diff --git a/guides/declare-static.md b/guides/declare-static.md new file mode 100644 index 0000000..87bad84 --- /dev/null +++ b/guides/declare-static.md @@ -0,0 +1,87 @@ +# Using `declare_static` + +`declare_static` marks content as statically analyzable so that the GT CLI can extract all possible translation entries at build time. + +## Overview + +`declare_static` is an identity function — it returns exactly what you pass in. Its purpose is as a **marker** for static analysis tools. When the GT CLI scans your code, it recognizes `declare_static(...)` calls and determines all possible return values, creating a separate translation entry for each. + +This is useful for: +- **Preserving word agreement** across languages (gender, plurality, etc.) +- **Reusable content** with function calls inside translated strings +- **Fragmented sentences** where part of the string is dynamic but has a known set of outcomes + +## Installation + +`declare_static` is exported from all GT Python packages: + +```python +from gt_fastapi import declare_static +# or +from gt_flask import declare_static +# or +from gt_i18n import declare_static +``` + +## Basic usage + +```python +from gt_fastapi import declare_static, t + +def get_subject(gender: str) -> str: + return "boy" if gender == "male" else "girl" + +# Without declare_static — the CLI can't know the possible values +message = t(f"The {get_subject(gender)} is playing.") + +# With declare_static — the CLI extracts both outcomes +message = t(f"The {declare_static(get_subject(gender))} is playing.") +``` + +With `declare_static`, the CLI creates two translation entries: +- `"The boy is playing."` → `"El niño está jugando."` +- `"The girl is playing."` → `"La niña está jugando."` + +Notice how agreement is handled automatically — Spanish uses "El" vs "La" depending on the subject. + +## How it works + +1. **At build time**, the GT CLI analyzes functions wrapped by `declare_static` +2. It determines all possible return values (these must be statically analyzable) +3. It creates separate translation entries for each unique outcome +4. **At runtime**, `declare_static` is just an identity function — it returns its argument unchanged + +## Combining with `declare_var` + +Use `declare_var` for truly dynamic content (user input, API data) inside a `declare_static` call: + +```python +from gt_fastapi import declare_static, declare_var, t + +def get_greeting(name: str | None) -> str: + if name: + return f"Hello, {declare_var(name)}" + return "Hello, stranger" + +message = t(f"{declare_static(get_greeting(name))}! How are you?") +``` + +## Inline expressions + +You can embed logic directly: + +```python +from gt_fastapi import declare_static, t + +message = t(f"The {declare_static('boy' if gender == 'male' else 'girl')} is playing.") +``` + +## Performance considerations + +`declare_static` multiplies translation entries. Each call with N possible outcomes creates N entries, and multiple `declare_static` calls in the same string multiply exponentially. Use judiciously. + +## Notes + +- All possible outcomes must be statically analyzable at build time +- Dynamic content (variables, API calls) inside `declare_static` should be wrapped with `declare_var` +- `decode_vars` can be used to extract original values from declared variables From bf65c49f016933978f622d92351f7d861f0818f2 Mon Sep 17 00:00:00 2001 From: moss-bryophyta <261561981+moss-bryophyta@users.noreply.github.com> Date: Sat, 7 Mar 2026 22:08:21 -0800 Subject: [PATCH 3/3] Revert "add guides for declare_static and custom get_locale" This reverts commit 877ec87ce56706c6f85ccf50adbd0fdade1bb11e. --- guides/custom-get-locale.md | 133 ------------------------------------ guides/declare-static.md | 87 ----------------------- 2 files changed, 220 deletions(-) delete mode 100644 guides/custom-get-locale.md delete mode 100644 guides/declare-static.md diff --git a/guides/custom-get-locale.md b/guides/custom-get-locale.md deleted file mode 100644 index 2e6129d..0000000 --- a/guides/custom-get-locale.md +++ /dev/null @@ -1,133 +0,0 @@ -# Custom locale detection with `get_locale` - -## Overview - -Both `gt_fastapi` and `gt_flask` accept a `get_locale` callback in `initialize_gt()` that controls how the user's locale is determined on each request. If you don't provide one, the default behavior parses the `Accept-Language` header. - -## Default behavior - -When no `get_locale` is provided, GT automatically: - -1. Reads the `Accept-Language` HTTP header from the incoming request (e.g. `en-US,en;q=0.9,es;q=0.8`) -2. Parses it into a list of locales sorted by quality value -3. Calls `determine_locale()` to find the best match against your configured `locales` -4. Falls back to `default_locale` if no match is found - -This means out of the box, users get content in whatever language their browser requests — as long as you have translations for it. - -## Providing a custom `get_locale` - -Pass a callable that takes the request object and returns a locale string: - -### FastAPI - -```python -from fastapi import FastAPI, Request -from gt_fastapi import initialize_gt - -app = FastAPI() - -def get_locale(request: Request) -> str: - """Detect locale from a query parameter, cookie, or header.""" - # 1. Check query parameter - locale = request.query_params.get("lang") - if locale: - return locale - - # 2. Check cookie - locale = request.cookies.get("locale") - if locale: - return locale - - # 3. Fall back to default - return "en" - -initialize_gt( - app, - default_locale="en", - locales=["es", "fr"], - get_locale=get_locale, -) -``` - -### Flask - -```python -from flask import Flask, request -from gt_flask import initialize_gt - -app = Flask(__name__) - -def get_locale(req) -> str: - """Detect locale from a query parameter, cookie, or header.""" - # 1. Check query parameter - locale = req.args.get("lang") - if locale: - return locale - - # 2. Check cookie - locale = req.cookies.get("locale") - if locale: - return locale - - # 3. Fall back to default - return "en" - -initialize_gt( - app, - default_locale="en", - locales=["es", "fr"], - get_locale=get_locale, -) -``` - -## How it's used internally - -When a request comes in: - -- **FastAPI**: The `gt_middleware` runs on every request. If `get_locale` is provided, it calls `get_locale(request)`. Otherwise it parses `Accept-Language`. -- **Flask**: A `before_request` hook runs on every request with the same logic. - -The resolved locale is then set on the `I18nManager`, which `t()` reads from when translating strings. - -## Common patterns - -### URL path prefix - -```python -def get_locale(request) -> str: - """Extract locale from URL path like /es/about or /fr/home.""" - parts = request.url.path.strip("/").split("/") - if parts and parts[0] in ("es", "fr", "de"): - return parts[0] - return "en" -``` - -### User profile / database - -```python -def get_locale(request) -> str: - """Look up locale from authenticated user's profile.""" - user = get_current_user(request) # your auth logic - if user and user.preferred_locale: - return user.preferred_locale - return "en" -``` - -### Subdomain - -```python -def get_locale(request) -> str: - """Detect locale from subdomain like es.example.com.""" - host = request.headers.get("host", "") - subdomain = host.split(".")[0] - if subdomain in ("es", "fr", "de"): - return subdomain - return "en" -``` - -## Notes - -- Your `get_locale` function should always return a valid locale string -- If it returns a locale you don't have translations for, `t()` will fall back to the original (default locale) content -- The function receives the raw request object — `Request` for FastAPI, `flask.request` for Flask diff --git a/guides/declare-static.md b/guides/declare-static.md deleted file mode 100644 index 87bad84..0000000 --- a/guides/declare-static.md +++ /dev/null @@ -1,87 +0,0 @@ -# Using `declare_static` - -`declare_static` marks content as statically analyzable so that the GT CLI can extract all possible translation entries at build time. - -## Overview - -`declare_static` is an identity function — it returns exactly what you pass in. Its purpose is as a **marker** for static analysis tools. When the GT CLI scans your code, it recognizes `declare_static(...)` calls and determines all possible return values, creating a separate translation entry for each. - -This is useful for: -- **Preserving word agreement** across languages (gender, plurality, etc.) -- **Reusable content** with function calls inside translated strings -- **Fragmented sentences** where part of the string is dynamic but has a known set of outcomes - -## Installation - -`declare_static` is exported from all GT Python packages: - -```python -from gt_fastapi import declare_static -# or -from gt_flask import declare_static -# or -from gt_i18n import declare_static -``` - -## Basic usage - -```python -from gt_fastapi import declare_static, t - -def get_subject(gender: str) -> str: - return "boy" if gender == "male" else "girl" - -# Without declare_static — the CLI can't know the possible values -message = t(f"The {get_subject(gender)} is playing.") - -# With declare_static — the CLI extracts both outcomes -message = t(f"The {declare_static(get_subject(gender))} is playing.") -``` - -With `declare_static`, the CLI creates two translation entries: -- `"The boy is playing."` → `"El niño está jugando."` -- `"The girl is playing."` → `"La niña está jugando."` - -Notice how agreement is handled automatically — Spanish uses "El" vs "La" depending on the subject. - -## How it works - -1. **At build time**, the GT CLI analyzes functions wrapped by `declare_static` -2. It determines all possible return values (these must be statically analyzable) -3. It creates separate translation entries for each unique outcome -4. **At runtime**, `declare_static` is just an identity function — it returns its argument unchanged - -## Combining with `declare_var` - -Use `declare_var` for truly dynamic content (user input, API data) inside a `declare_static` call: - -```python -from gt_fastapi import declare_static, declare_var, t - -def get_greeting(name: str | None) -> str: - if name: - return f"Hello, {declare_var(name)}" - return "Hello, stranger" - -message = t(f"{declare_static(get_greeting(name))}! How are you?") -``` - -## Inline expressions - -You can embed logic directly: - -```python -from gt_fastapi import declare_static, t - -message = t(f"The {declare_static('boy' if gender == 'male' else 'girl')} is playing.") -``` - -## Performance considerations - -`declare_static` multiplies translation entries. Each call with N possible outcomes creates N entries, and multiple `declare_static` calls in the same string multiply exponentially. Use judiciously. - -## Notes - -- All possible outcomes must be statically analyzable at build time -- Dynamic content (variables, API calls) inside `declare_static` should be wrapped with `declare_var` -- `decode_vars` can be used to extract original values from declared variables