From bdf8a99c99677e2e3e93af45d2ec94833ec11c62 Mon Sep 17 00:00:00 2001 From: Ernest McCarter Date: Sat, 7 Mar 2026 14:50:38 -0800 Subject: [PATCH 1/4] feat: locale functionalities --- .../gt-fastapi/src/gt_fastapi/__init__.py | 7 +-- packages/gt-fastapi/src/gt_fastapi/_setup.py | 42 +++--------------- packages/gt-flask/src/gt_flask/__init__.py | 7 +-- packages/gt-flask/src/gt_flask/_setup.py | 43 +++---------------- packages/gt-i18n/src/gt_i18n/__init__.py | 9 ++++ .../gt-i18n/src/gt_i18n/helpers/_locales.py | 16 +++++++ .../src/gt_i18n/i18n_manager/_i18n_manager.py | 15 +++++++ .../gt-i18n/src/gt_i18n/internal/__init__.py | 5 +++ .../internal/_detect_from_accept_language.py | 33 ++++++++++++++ pyproject.toml | 5 +++ 10 files changed, 101 insertions(+), 81 deletions(-) create mode 100644 packages/gt-i18n/src/gt_i18n/helpers/_locales.py create mode 100644 packages/gt-i18n/src/gt_i18n/internal/__init__.py create mode 100644 packages/gt-i18n/src/gt_i18n/internal/_detect_from_accept_language.py diff --git a/packages/gt-fastapi/src/gt_fastapi/__init__.py b/packages/gt-fastapi/src/gt_fastapi/__init__.py index 49168dd..ef88ede 100644 --- a/packages/gt-fastapi/src/gt_fastapi/__init__.py +++ b/packages/gt-fastapi/src/gt_fastapi/__init__.py @@ -1,7 +1,8 @@ """FastAPI integration for General Translation.""" -from gt_i18n import declare_static, declare_var, decode_vars, t - +from gt_i18n import ( + declare_static, declare_var, decode_vars, t, get_locale, get_locales, get_default_locale +) from gt_fastapi._setup import initialize_gt -__all__ = ["initialize_gt", "t", "declare_var", "declare_static", "decode_vars"] +__all__ = ["initialize_gt", "t", "declare_var", "declare_static", "decode_vars", "get_locale", "get_locales", "get_default_locale"] diff --git a/packages/gt-fastapi/src/gt_fastapi/_setup.py b/packages/gt-fastapi/src/gt_fastapi/_setup.py index 3f0fcf6..a58703e 100644 --- a/packages/gt-fastapi/src/gt_fastapi/_setup.py +++ b/packages/gt-fastapi/src/gt_fastapi/_setup.py @@ -6,53 +6,21 @@ from contextlib import asynccontextmanager from typing import Any -from generaltranslation.locales import determine_locale -from gt_i18n import I18nManager, set_i18n_manager, t # noqa: F401 - - -def _detect_from_accept_language(request: Any, manager: I18nManager) -> str: - """Parse Accept-Language header and resolve against configured locales.""" - accept = request.headers.get("accept-language", "") - if not accept: - return manager.default_locale - - locales: list[tuple[float, str]] = [] - for part in accept.split(","): - part = part.strip() - if not part: - continue - if ";q=" in part: - lang, q = part.split(";q=", 1) - try: - quality = float(q.strip()) - except ValueError: - quality = 0.0 - locales.append((quality, lang.strip())) - else: - locales.append((1.0, part)) - - locales.sort(key=lambda x: x[0], reverse=True) - locale_list = [loc for _, loc in locales] - - approved = manager.get_locales() - if not approved: - return locale_list[0] if locale_list else manager.default_locale - - result = determine_locale(locale_list, approved) - return result or manager.default_locale - +from gt_i18n import I18nManager, set_i18n_manager +from gt_i18n.internal import _detect_from_accept_language +from generaltranslation import CustomMapping def initialize_gt( app: Any, *, default_locale: str = "en", locales: list[str] | None = None, + custom_mapping: CustomMapping | None = None, project_id: str | None = None, cache_url: str | None = None, get_locale: Callable[..., str] | None = None, load_translations: Callable[[str], dict[str, str]] | None = None, eager_loading: bool = True, - **kwargs: Any, ) -> I18nManager: """Initialize General Translation for a FastAPI app. @@ -73,10 +41,10 @@ def initialize_gt( manager = I18nManager( default_locale=default_locale, locales=locales, + custom_mapping=custom_mapping, project_id=project_id, cache_url=cache_url, load_translations=load_translations, - **kwargs, ) set_i18n_manager(manager) diff --git a/packages/gt-flask/src/gt_flask/__init__.py b/packages/gt-flask/src/gt_flask/__init__.py index 0d0b6a9..7595d49 100644 --- a/packages/gt-flask/src/gt_flask/__init__.py +++ b/packages/gt-flask/src/gt_flask/__init__.py @@ -1,7 +1,8 @@ """Flask integration for General Translation.""" -from gt_i18n import t - +from gt_i18n import ( + declare_static, declare_var, decode_vars, t, get_locale, get_locales, get_default_locale +) from gt_flask._setup import initialize_gt -__all__ = ["initialize_gt", "t"] +__all__ = ["initialize_gt", "t", "declare_var", "declare_static", "decode_vars", "get_locale", "get_locales", "get_default_locale"] diff --git a/packages/gt-flask/src/gt_flask/_setup.py b/packages/gt-flask/src/gt_flask/_setup.py index 5268f58..28875e7 100644 --- a/packages/gt-flask/src/gt_flask/_setup.py +++ b/packages/gt-flask/src/gt_flask/_setup.py @@ -6,42 +6,9 @@ from collections.abc import Callable from typing import Any -from generaltranslation.locales import determine_locale -from gt_i18n import I18nManager, set_i18n_manager, t # noqa: F401 - - -def _detect_from_accept_language(request: Any, manager: I18nManager) -> str: - """Parse Accept-Language header and resolve against configured locales.""" - accept = request.headers.get("Accept-Language", "") - if not accept: - return manager.default_locale - - # Parse Accept-Language: e.g. "en-US,en;q=0.9,es;q=0.8" - locales: list[tuple[float, str]] = [] - for part in accept.split(","): - part = part.strip() - if not part: - continue - if ";q=" in part: - lang, q = part.split(";q=", 1) - try: - quality = float(q.strip()) - except ValueError: - quality = 0.0 - locales.append((quality, lang.strip())) - else: - locales.append((1.0, part)) - - # Sort by quality descending - locales.sort(key=lambda x: x[0], reverse=True) - locale_list = [loc for _, loc in locales] - - approved = manager.get_locales() - if not approved: - return locale_list[0] if locale_list else manager.default_locale - - result = determine_locale(locale_list, approved) - return result or manager.default_locale +from gt_i18n import I18nManager, set_i18n_manager +from gt_i18n.internal import _detect_from_accept_language +from generaltranslation import CustomMapping def initialize_gt( @@ -49,12 +16,12 @@ def initialize_gt( *, default_locale: str = "en", locales: list[str] | None = None, + custom_mapping: CustomMapping | None = None, project_id: str | None = None, cache_url: str | None = None, get_locale: Callable[..., str] | None = None, load_translations: Callable[[str], dict[str, str]] | None = None, eager_loading: bool = True, - **kwargs: Any, ) -> I18nManager: """Initialize General Translation for a Flask app. @@ -75,10 +42,10 @@ def initialize_gt( manager = I18nManager( default_locale=default_locale, locales=locales, + custom_mapping=custom_mapping, project_id=project_id, cache_url=cache_url, load_translations=load_translations, - **kwargs, ) set_i18n_manager(manager) diff --git a/packages/gt-i18n/src/gt_i18n/__init__.py b/packages/gt-i18n/src/gt_i18n/__init__.py index 0715702..cecf533 100644 --- a/packages/gt-i18n/src/gt_i18n/__init__.py +++ b/packages/gt-i18n/src/gt_i18n/__init__.py @@ -21,6 +21,11 @@ t, t_fallback, ) +from gt_i18n.helpers._locales import ( + get_locale, + get_locales, + get_default_locale, +) __all__ = [ # I18nManager @@ -40,6 +45,10 @@ "msg", "t", "t_fallback", + # Locale helpers + "get_locale", + "get_locales", + "get_default_locale", # Static variable helpers "declare_var", "declare_static", diff --git a/packages/gt-i18n/src/gt_i18n/helpers/_locales.py b/packages/gt-i18n/src/gt_i18n/helpers/_locales.py new file mode 100644 index 0000000..4da9265 --- /dev/null +++ b/packages/gt-i18n/src/gt_i18n/helpers/_locales.py @@ -0,0 +1,16 @@ +from gt_i18n.i18n_manager._singleton import get_i18n_manager + +def get_locale() -> str: + """Get the current locale from the I18nManager.""" + manager = get_i18n_manager() + return manager.get_locale() + +def get_locales() -> list[str]: + """Get the locales from the I18nManager.""" + manager = get_i18n_manager() + return manager.get_locales() + +def get_default_locale() -> str: + """Get the default locale from the I18nManager.""" + manager = get_i18n_manager() + return manager.default_locale \ No newline at end of file diff --git a/packages/gt-i18n/src/gt_i18n/i18n_manager/_i18n_manager.py b/packages/gt-i18n/src/gt_i18n/i18n_manager/_i18n_manager.py index d9c6ced..095d110 100644 --- a/packages/gt-i18n/src/gt_i18n/i18n_manager/_i18n_manager.py +++ b/packages/gt-i18n/src/gt_i18n/i18n_manager/_i18n_manager.py @@ -4,6 +4,8 @@ from typing import TYPE_CHECKING +from generaltranslation import CustomMapping +from generaltranslation._gt import GT from generaltranslation._settings import LIBRARY_DEFAULT_LOCALE from generaltranslation.locales import requires_translation @@ -37,6 +39,7 @@ def __init__( locales: list[str] | None = None, project_id: str | None = None, cache_url: str | None = None, + custom_mapping: CustomMapping | None = None, store_adapter: StorageAdapter | None = None, load_translations: TranslationsLoader | None = None, cache_expiry_time: int = 60_000, @@ -44,6 +47,8 @@ def __init__( self._default_locale = default_locale self._locales = locales or [] self._project_id = project_id + self._cache_url = cache_url + self._custom_mapping = custom_mapping # Storage self._store: StorageAdapter = store_adapter or ContextVarStorageAdapter() @@ -66,6 +71,16 @@ def __init__( def default_locale(self) -> str: return self._default_locale + def get_gt_instance(self) -> GT: + """Get a new GT instance for the current request.""" + return GT( + project_id=self._project_id, + source_locale=self._default_locale, + target_locale=self.get_locale(), + locales=self._locales, + custom_mapping=self._custom_mapping, + ) + def get_locales(self) -> list[str]: return list(self._locales) diff --git a/packages/gt-i18n/src/gt_i18n/internal/__init__.py b/packages/gt-i18n/src/gt_i18n/internal/__init__.py new file mode 100644 index 0000000..cf17c2c --- /dev/null +++ b/packages/gt-i18n/src/gt_i18n/internal/__init__.py @@ -0,0 +1,5 @@ +from gt_i18n.internal._detect_from_accept_language import _detect_from_accept_language + +__all__ = [ + "_detect_from_accept_language", +] \ No newline at end of file diff --git a/packages/gt-i18n/src/gt_i18n/internal/_detect_from_accept_language.py b/packages/gt-i18n/src/gt_i18n/internal/_detect_from_accept_language.py new file mode 100644 index 0000000..a7c59e6 --- /dev/null +++ b/packages/gt-i18n/src/gt_i18n/internal/_detect_from_accept_language.py @@ -0,0 +1,33 @@ +from typing import Any +from gt_i18n.i18n_manager._i18n_manager import I18nManager + +def _detect_from_accept_language(request: Any, manager: I18nManager) -> str: + """Default, Parse Accept-Language header and resolve against configured locales.""" + accept = request.headers.get("Accept-Language", "") + if not accept: + return manager.default_locale + + # Parse Accept-Language: e.g. "en-US,en;q=0.9,es;q=0.8" + locales: list[tuple[float, str]] = [] + for part in accept.split(","): + part = part.strip() + if not part: + continue + if ";q=" in part: + lang, q = part.split(";q=", 1) + try: + quality = float(q.strip()) + except ValueError: + quality = 0.0 + locales.append((quality, lang.strip())) + else: + locales.append((1.0, part)) + + # Sort by quality descending + locales.sort(key=lambda x: x[0], reverse=True) + locale_list = [loc for _, loc in locales] + + # Determine the best matching locale + gt = manager.get_gt_instance() + result = gt.determine_locale(locale_list) + return result or manager.default_locale \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 34e5828..a0a41b1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,6 +31,11 @@ select = ["E", "F", "I", "N", "W", "UP"] [tool.ruff.lint.per-file-ignores] "**/tests/**" = ["E501"] +[tool.basedpyright] +typeCheckingMode = "standard" +pythonVersion = "3.10" +exclude = ["examples/"] + [tool.mypy] python_version = "3.10" warn_return_any = false From 53a44eb9596b2c39baec7664053b398ce9af8a6c Mon Sep 17 00:00:00 2001 From: Ernest McCarter Date: Sat, 7 Mar 2026 14:51:54 -0800 Subject: [PATCH 2/4] chore: changeset --- .sampo/changesets/noble-stormcaller-lempo.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .sampo/changesets/noble-stormcaller-lempo.md diff --git a/.sampo/changesets/noble-stormcaller-lempo.md b/.sampo/changesets/noble-stormcaller-lempo.md new file mode 100644 index 0000000..7069dda --- /dev/null +++ b/.sampo/changesets/noble-stormcaller-lempo.md @@ -0,0 +1,7 @@ +--- +pypi/gt-fastapi: minor +pypi/gt-flask: minor +pypi/gt-i18n: minor +--- + +feat: add expected functionalities From 0e8e527dc8b4f7386c267b71e8a3796253f64c28 Mon Sep 17 00:00:00 2001 From: Ernest McCarter Date: Sat, 7 Mar 2026 14:54:49 -0800 Subject: [PATCH 3/4] chore: lint --- packages/gt-fastapi/src/gt_fastapi/__init__.py | 16 ++++++++++++---- packages/gt-fastapi/src/gt_fastapi/_setup.py | 3 ++- packages/gt-flask/src/gt_flask/__init__.py | 16 ++++++++++++---- packages/gt-flask/src/gt_flask/_setup.py | 2 +- packages/gt-i18n/src/gt_i18n/__init__.py | 10 +++++----- packages/gt-i18n/src/gt_i18n/helpers/_locales.py | 3 ++- .../gt-i18n/src/gt_i18n/internal/__init__.py | 2 +- .../internal/_detect_from_accept_language.py | 4 +++- 8 files changed, 38 insertions(+), 18 deletions(-) diff --git a/packages/gt-fastapi/src/gt_fastapi/__init__.py b/packages/gt-fastapi/src/gt_fastapi/__init__.py index ef88ede..93517d4 100644 --- a/packages/gt-fastapi/src/gt_fastapi/__init__.py +++ b/packages/gt-fastapi/src/gt_fastapi/__init__.py @@ -1,8 +1,16 @@ """FastAPI integration for General Translation.""" -from gt_i18n import ( - declare_static, declare_var, decode_vars, t, get_locale, get_locales, get_default_locale -) +from gt_i18n import declare_static, declare_var, decode_vars, get_default_locale, get_locale, get_locales, t + from gt_fastapi._setup import initialize_gt -__all__ = ["initialize_gt", "t", "declare_var", "declare_static", "decode_vars", "get_locale", "get_locales", "get_default_locale"] +__all__ = [ + "initialize_gt", + "t", + "declare_var", + "declare_static", + "decode_vars", + "get_locale", + "get_locales", + "get_default_locale", +] diff --git a/packages/gt-fastapi/src/gt_fastapi/_setup.py b/packages/gt-fastapi/src/gt_fastapi/_setup.py index ae548dc..eeb289f 100644 --- a/packages/gt-fastapi/src/gt_fastapi/_setup.py +++ b/packages/gt-fastapi/src/gt_fastapi/_setup.py @@ -6,10 +6,11 @@ from contextlib import asynccontextmanager from typing import Any +from generaltranslation import CustomMapping from generaltranslation._settings import LIBRARY_DEFAULT_LOCALE from gt_i18n import I18nManager, set_i18n_manager from gt_i18n.internal import _detect_from_accept_language -from generaltranslation import CustomMapping + def initialize_gt( app: Any, diff --git a/packages/gt-flask/src/gt_flask/__init__.py b/packages/gt-flask/src/gt_flask/__init__.py index 7595d49..4b5a22e 100644 --- a/packages/gt-flask/src/gt_flask/__init__.py +++ b/packages/gt-flask/src/gt_flask/__init__.py @@ -1,8 +1,16 @@ """Flask integration for General Translation.""" -from gt_i18n import ( - declare_static, declare_var, decode_vars, t, get_locale, get_locales, get_default_locale -) +from gt_i18n import declare_static, declare_var, decode_vars, get_default_locale, get_locale, get_locales, t + from gt_flask._setup import initialize_gt -__all__ = ["initialize_gt", "t", "declare_var", "declare_static", "decode_vars", "get_locale", "get_locales", "get_default_locale"] +__all__ = [ + "initialize_gt", + "t", + "declare_var", + "declare_static", + "decode_vars", + "get_locale", + "get_locales", + "get_default_locale", +] diff --git a/packages/gt-flask/src/gt_flask/_setup.py b/packages/gt-flask/src/gt_flask/_setup.py index e573107..fd79288 100644 --- a/packages/gt-flask/src/gt_flask/_setup.py +++ b/packages/gt-flask/src/gt_flask/_setup.py @@ -6,10 +6,10 @@ from collections.abc import Callable from typing import Any +from generaltranslation import CustomMapping from generaltranslation._settings import LIBRARY_DEFAULT_LOCALE from gt_i18n import I18nManager, set_i18n_manager from gt_i18n.internal import _detect_from_accept_language -from generaltranslation import CustomMapping def initialize_gt( diff --git a/packages/gt-i18n/src/gt_i18n/__init__.py b/packages/gt-i18n/src/gt_i18n/__init__.py index cecf533..4e8a066 100644 --- a/packages/gt-i18n/src/gt_i18n/__init__.py +++ b/packages/gt-i18n/src/gt_i18n/__init__.py @@ -2,6 +2,11 @@ from generaltranslation.static import declare_static, declare_var, decode_vars +from gt_i18n.helpers._locales import ( + get_default_locale, + get_locale, + get_locales, +) from gt_i18n.i18n_manager import ( ContextVarStorageAdapter, I18nManager, @@ -21,11 +26,6 @@ t, t_fallback, ) -from gt_i18n.helpers._locales import ( - get_locale, - get_locales, - get_default_locale, -) __all__ = [ # I18nManager diff --git a/packages/gt-i18n/src/gt_i18n/helpers/_locales.py b/packages/gt-i18n/src/gt_i18n/helpers/_locales.py index 4da9265..cb166c0 100644 --- a/packages/gt-i18n/src/gt_i18n/helpers/_locales.py +++ b/packages/gt-i18n/src/gt_i18n/helpers/_locales.py @@ -1,5 +1,6 @@ from gt_i18n.i18n_manager._singleton import get_i18n_manager + def get_locale() -> str: """Get the current locale from the I18nManager.""" manager = get_i18n_manager() @@ -13,4 +14,4 @@ def get_locales() -> list[str]: def get_default_locale() -> str: """Get the default locale from the I18nManager.""" manager = get_i18n_manager() - return manager.default_locale \ No newline at end of file + return manager.default_locale diff --git a/packages/gt-i18n/src/gt_i18n/internal/__init__.py b/packages/gt-i18n/src/gt_i18n/internal/__init__.py index cf17c2c..c6cc4db 100644 --- a/packages/gt-i18n/src/gt_i18n/internal/__init__.py +++ b/packages/gt-i18n/src/gt_i18n/internal/__init__.py @@ -2,4 +2,4 @@ __all__ = [ "_detect_from_accept_language", -] \ No newline at end of file +] diff --git a/packages/gt-i18n/src/gt_i18n/internal/_detect_from_accept_language.py b/packages/gt-i18n/src/gt_i18n/internal/_detect_from_accept_language.py index a7c59e6..7b44777 100644 --- a/packages/gt-i18n/src/gt_i18n/internal/_detect_from_accept_language.py +++ b/packages/gt-i18n/src/gt_i18n/internal/_detect_from_accept_language.py @@ -1,6 +1,8 @@ from typing import Any + from gt_i18n.i18n_manager._i18n_manager import I18nManager + def _detect_from_accept_language(request: Any, manager: I18nManager) -> str: """Default, Parse Accept-Language header and resolve against configured locales.""" accept = request.headers.get("Accept-Language", "") @@ -30,4 +32,4 @@ def _detect_from_accept_language(request: Any, manager: I18nManager) -> str: # Determine the best matching locale gt = manager.get_gt_instance() result = gt.determine_locale(locale_list) - return result or manager.default_locale \ No newline at end of file + return result or manager.default_locale From 03b962e1f6fb8742b89a3819bf3b2718e512912e Mon Sep 17 00:00:00 2001 From: Ernest McCarter Date: Sat, 7 Mar 2026 14:55:48 -0800 Subject: [PATCH 4/4] chore: lint --- packages/gt-fastapi/src/gt_fastapi/__init__.py | 16 ++++++++-------- packages/gt-flask/src/gt_flask/__init__.py | 16 ++++++++-------- packages/gt-i18n/src/gt_i18n/helpers/_locales.py | 2 ++ 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/packages/gt-fastapi/src/gt_fastapi/__init__.py b/packages/gt-fastapi/src/gt_fastapi/__init__.py index 93517d4..9e79b27 100644 --- a/packages/gt-fastapi/src/gt_fastapi/__init__.py +++ b/packages/gt-fastapi/src/gt_fastapi/__init__.py @@ -5,12 +5,12 @@ from gt_fastapi._setup import initialize_gt __all__ = [ - "initialize_gt", - "t", - "declare_var", - "declare_static", - "decode_vars", - "get_locale", - "get_locales", - "get_default_locale", + "initialize_gt", + "t", + "declare_var", + "declare_static", + "decode_vars", + "get_locale", + "get_locales", + "get_default_locale", ] diff --git a/packages/gt-flask/src/gt_flask/__init__.py b/packages/gt-flask/src/gt_flask/__init__.py index 4b5a22e..5b72fec 100644 --- a/packages/gt-flask/src/gt_flask/__init__.py +++ b/packages/gt-flask/src/gt_flask/__init__.py @@ -5,12 +5,12 @@ from gt_flask._setup import initialize_gt __all__ = [ - "initialize_gt", - "t", - "declare_var", - "declare_static", - "decode_vars", - "get_locale", - "get_locales", - "get_default_locale", + "initialize_gt", + "t", + "declare_var", + "declare_static", + "decode_vars", + "get_locale", + "get_locales", + "get_default_locale", ] diff --git a/packages/gt-i18n/src/gt_i18n/helpers/_locales.py b/packages/gt-i18n/src/gt_i18n/helpers/_locales.py index cb166c0..ff0a00a 100644 --- a/packages/gt-i18n/src/gt_i18n/helpers/_locales.py +++ b/packages/gt-i18n/src/gt_i18n/helpers/_locales.py @@ -6,11 +6,13 @@ def get_locale() -> str: manager = get_i18n_manager() return manager.get_locale() + def get_locales() -> list[str]: """Get the locales from the I18nManager.""" manager = get_i18n_manager() return manager.get_locales() + def get_default_locale() -> str: """Get the default locale from the I18nManager.""" manager = get_i18n_manager()