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 diff --git a/packages/gt-fastapi/src/gt_fastapi/__init__.py b/packages/gt-fastapi/src/gt_fastapi/__init__.py index 49168dd..9e79b27 100644 --- a/packages/gt-fastapi/src/gt_fastapi/__init__.py +++ b/packages/gt-fastapi/src/gt_fastapi/__init__.py @@ -1,7 +1,16 @@ """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, 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"] +__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 4717ea6..eeb289f 100644 --- a/packages/gt-fastapi/src/gt_fastapi/_setup.py +++ b/packages/gt-fastapi/src/gt_fastapi/_setup.py @@ -6,41 +6,10 @@ from contextlib import asynccontextmanager from typing import Any +from generaltranslation import CustomMapping from generaltranslation._settings import LIBRARY_DEFAULT_LOCALE -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 def initialize_gt( @@ -48,12 +17,12 @@ def initialize_gt( *, default_locale: str = LIBRARY_DEFAULT_LOCALE, 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. @@ -74,10 +43,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..5b72fec 100644 --- a/packages/gt-flask/src/gt_flask/__init__.py +++ b/packages/gt-flask/src/gt_flask/__init__.py @@ -1,7 +1,16 @@ """Flask integration for General Translation.""" -from gt_i18n import t +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"] +__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 0cf17ed..fd79288 100644 --- a/packages/gt-flask/src/gt_flask/_setup.py +++ b/packages/gt-flask/src/gt_flask/_setup.py @@ -6,43 +6,10 @@ from collections.abc import Callable from typing import Any +from generaltranslation import CustomMapping from generaltranslation._settings import LIBRARY_DEFAULT_LOCALE -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 def initialize_gt( @@ -50,12 +17,12 @@ def initialize_gt( *, default_locale: str = LIBRARY_DEFAULT_LOCALE, 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. @@ -76,10 +43,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..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, @@ -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..ff0a00a --- /dev/null +++ b/packages/gt-i18n/src/gt_i18n/helpers/_locales.py @@ -0,0 +1,19 @@ +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 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..c6cc4db --- /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", +] 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..7b44777 --- /dev/null +++ b/packages/gt-i18n/src/gt_i18n/internal/_detect_from_accept_language.py @@ -0,0 +1,35 @@ +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 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