|
1 | | -# i18n_core: Internationalization Utilities |
2 | | - |
3 | | -`i18n_core` provides a set of utilities to simplify the process of internationalization (i18n) and localization (l10n) for Python applications. It builds upon the standard `locale` module and the powerful `babel` library to offer a streamlined way to manage translations. |
4 | | - |
5 | | -## Features |
6 | | - |
7 | | -* **Global & Module Translations:** Easily load translations for your main application and individual component libraries or modules. |
8 | | -* **System Locale Detection:** Automatically detects the user's system locale on Windows, macOS, and Linux. |
9 | | -* **Babel Integration:** Leverages `babel` for handling `.mo` translation files and locale data. |
10 | | -* **WxPython Support:** Includes helpers specifically for integrating translations with WxPython applications (`i18n_core.gui`). |
11 | | -* **Frozen Environment Support:** Patches `babel` to correctly locate locale data when running in frozen environments (e.g., PyInstaller). |
12 | | -* **Context Manager:** Provides a `reset_locale` context manager to temporarily change the locale. |
13 | | - |
14 | | -## Installation |
15 | | - |
16 | | -```bash |
17 | | -pip install i18n_core |
18 | | -``` |
19 | | -*(Assuming the package is available on PyPI. If it's local, adjust accordingly)* |
20 | | - |
21 | | -## Dependencies |
22 | | - |
23 | | -* [Babel](https://babel.pocoo.org/) |
24 | | -* [platform_utils](https://github.com/your_org/platform_utils) *(Assuming this is another internal or specific library)* |
25 | | - |
26 | | -## Usage |
27 | | - |
28 | | -### Initializing Global Translation |
29 | | - |
30 | | -Typically, in your application's entry point, you'll install the global translation. This sets up the primary language and translation domain for your app. |
31 | | - |
32 | | -```python |
33 | | -import i18n_core |
34 | | -import os |
35 | | - |
36 | | -# Assuming your locale files are in a 'locale' subdirectory |
37 | | -locale_dir = os.path.join(os.path.dirname(__file__), 'locale') |
38 | | -app_domain = 'my_app' # Corresponds to my_app.mo files |
39 | | - |
40 | | -# Detect system locale or specify one, e.g., 'es_ES' |
41 | | -# Loads translations from locale_dir/<locale_id>/LC_MESSAGES/my_app.mo |
42 | | -current_locale = i18n_core.install_global_translation( |
43 | | - domain=app_domain, |
44 | | - locale_path=locale_dir |
45 | | - # locale_id='fr_FR' # Optionally force a locale |
46 | | -) |
47 | | - |
48 | | -print(f"Application locale set to: {current_locale}") |
49 | | - |
50 | | -# Now you can use the standard gettext functions globally |
51 | | -print(_("Hello, world!")) |
52 | | -``` |
53 | | - |
54 | | -### Loading Module Translations |
55 | | - |
56 | | -If you have separate libraries or modules with their own translations, you can merge them into the active global translation. |
57 | | - |
58 | | -```python |
59 | | -import i18n_core |
60 | | -import my_module |
61 | | -import os |
62 | | - |
63 | | -# Assuming my_module has its own 'locale' subdirectory |
64 | | -module_locale_dir = os.path.join(os.path.dirname(my_module.__file__), 'locale') |
65 | | -module_domain = 'my_module' # Corresponds to my_module.mo |
66 | | - |
67 | | -i18n_core.install_module_translation( |
68 | | - domain=module_domain, |
69 | | - locale_path=module_locale_dir, |
70 | | - module=my_module # Or provide module name as string 'my_module' |
71 | | - # Uses the currently active global locale by default |
72 | | -) |
73 | | - |
74 | | -# Strings from my_module's domain are now also available via _() |
75 | | -print(_("Module-specific string")) |
76 | | -``` |
77 | | - |
78 | | -### WxPython Integration |
79 | | - |
80 | | -The `i18n_core.gui` module helps initialize WxPython's internal translation mechanisms. |
81 | | - |
82 | | -```python |
83 | | -import wx |
84 | | -import i18n_core |
85 | | -import i18n_core.gui |
86 | | -import os |
87 | | - |
88 | | -app = wx.App() |
89 | | - |
90 | | -# After installing global translation with i18n_core.install_global_translation... |
91 | | -locale_dir = i18n_core.application_locale_path # Get path used by global install |
92 | | -current_locale = i18n_core.CURRENT_LOCALE |
93 | | - |
94 | | -# Initialize wx.Locale |
95 | | -wx_locale = i18n_core.gui.set_wx_locale(locale_dir, current_locale) |
96 | | - |
97 | | -if not wx_locale: |
98 | | - print("Failed to set Wx locale.") |
99 | | - |
100 | | -# ... proceed with your WxPython application setup ... |
101 | | - |
102 | | -app.MainLoop() |
103 | | -``` |
104 | | - |
105 | | -## Locale Management |
106 | | - |
107 | | -* `get_system_locale()`: Detects the OS locale. |
108 | | -* `set_locale(locale_id)`: Sets the locale for the current process/thread. |
109 | | -* `get_available_locales(domain, locale_path)`: Lists available `babel.Locale` objects based on found translation files. |
110 | | -* `reset_locale()`: A context manager to temporarily change the locale and restore it afterwards. |
111 | | - |
112 | | -```python |
113 | | -from i18n_core import reset_locale, set_locale |
114 | | - |
115 | | -print(f"Current locale: {i18n_core.CURRENT_LOCALE}") |
116 | | - |
117 | | -with reset_locale(): |
118 | | - set_locale('fr_FR') |
119 | | - print(f"Locale inside context: {i18n_core.CURRENT_LOCALE}") |
120 | | - # Perform operations requiring French locale |
121 | | - |
122 | | -print(f"Locale after context: {i18n_core.CURRENT_LOCALE}") |
123 | | -``` |
124 | | - |
125 | | -## Contributing |
126 | | - |
127 | | -Please refer to CONTRIBUTING.md for details. *(Optional: Create this file if needed)* |
128 | | - |
129 | | -## License |
130 | | - |
131 | | -This project is licensed under the terms of the LICENSE file. |
| 1 | +# i18n_core: Internationalization Utilities |
| 2 | + |
| 3 | +`i18n_core` is a lightweight, order-agnostic i18n layer on top of Babel. |
| 4 | +It lets apps and libraries register translations at any time, and all lookups |
| 5 | +react to locale changes without re-registration. |
| 6 | + |
| 7 | +## Features |
| 8 | + |
| 9 | +- Dynamic lookups: `_`, `__`, `ngettext` reflect the current locale. |
| 10 | +- Order-agnostic: libraries can register before or after the app; no merge-by-call-order. |
| 11 | +- Domain-aware: no cross-domain bleed; use per-domain catalogs with explicit precedence. |
| 12 | +- System locale detection and Windows LCID support. |
| 13 | +- WxPython helpers: `i18n_core.gui.set_wx_locale`. |
| 14 | +- Frozen env support (works with embedded data paths). |
| 15 | + |
| 16 | +## Installation |
| 17 | + |
| 18 | +```bash |
| 19 | +pip install i18n_core |
| 20 | +``` |
| 21 | + |
| 22 | +## Dependencies |
| 23 | + |
| 24 | +- Babel (>= 2.15, < 3.0) |
| 25 | +- platform_utils |
| 26 | + |
| 27 | +## Quickstart (App) |
| 28 | + |
| 29 | +Use `finalize_i18n` once at startup to set the default domain and locale. |
| 30 | + |
| 31 | +```python |
| 32 | +import os |
| 33 | +from i18n_core import finalize_i18n, get_system_locale, _ |
| 34 | + |
| 35 | +locale_dir = os.path.join(os.path.dirname(__file__), "locale") |
| 36 | +finalize_i18n( |
| 37 | + locale_id=get_system_locale(), |
| 38 | + app_domain="my_app", # matches your .mo domain |
| 39 | + app_locale_path=locale_dir, # <locale>/<LCID>/LC_MESSAGES/my_app.mo |
| 40 | +) |
| 41 | + |
| 42 | +print(_("Hello, world!")) |
| 43 | +``` |
| 44 | + |
| 45 | +Change locale anytime — all lookups update automatically: |
| 46 | + |
| 47 | +```python |
| 48 | +from i18n_core import set_locale |
| 49 | + |
| 50 | +set_locale("de_DE") |
| 51 | +``` |
| 52 | + |
| 53 | +## Libraries |
| 54 | + |
| 55 | +Two options — both are safe and idempotent: |
| 56 | + |
| 57 | +- Rely on inference (zero code): just `from i18n_core import _` and call it. |
| 58 | + The top-level package name becomes your domain, and `<pkg>/locale` is used. |
| 59 | + |
| 60 | +- Explicit registration (recommended for clarity/perf): |
| 61 | + |
| 62 | +```python |
| 63 | +import sys |
| 64 | +import i18n_core |
| 65 | + |
| 66 | +i18n_core.install_module_translation( |
| 67 | + domain="my_lib", # your .mo domain |
| 68 | + module=sys.modules[__name__], |
| 69 | + # locale_path=<path> # optional; defaults to <pkg>/locale |
| 70 | +) |
| 71 | + |
| 72 | +print(_("A library string")) |
| 73 | +``` |
| 74 | + |
| 75 | +## WxPython Integration |
| 76 | + |
| 77 | +```python |
| 78 | +import wx |
| 79 | +import i18n_core.gui |
| 80 | + |
| 81 | +app = wx.App() |
| 82 | +wx_locale = i18n_core.gui.set_wx_locale(locale_path, locale_id) |
| 83 | +``` |
| 84 | + |
| 85 | +## APIs |
| 86 | + |
| 87 | +- `finalize_i18n(locale_id=None, languages=None, app_domain=None, app_locale_path=None, install_into_builtins=True, priority=100) -> str` |
| 88 | + - Configure default app domain/locale; install builtins wrappers. |
| 89 | +- `install_module_translation(domain=None, locale_id=None, locale_path=None, module=None, priority=50) -> None` |
| 90 | + - Register a provider for a domain and install wrappers into a specific module. |
| 91 | +- `install_global_translation(domain, locale_id=None, locale_path=None) -> str` |
| 92 | + - Back-compat shim: registers default domain and sets locale. |
| 93 | +- `set_locale(locale_id: str) -> str` |
| 94 | + - Normalize and apply process/Windows locale; updates i18n registry. |
| 95 | +- `get_available_translations(domain, locale_path=None) -> Iterable[str]` |
| 96 | + - List available locales for a domain across registered paths. |
| 97 | +- `get_available_locales(domain, locale_path=None) -> Iterable[babel.core.Locale]` |
| 98 | +- `reset_locale()` context manager: temporarily adjust process locale. |
| 99 | + |
| 100 | +## Precedence & Domains |
| 101 | + |
| 102 | +- Precedence is explicit: higher `priority` overrides lower within a domain |
| 103 | + (default: app=100, libraries=50). Ties resolve by first-registration order. |
| 104 | +- Lookups are domain-aware; module wrappers use their bound domain, and builtins |
| 105 | + use the default domain (or fall back to caller inference). |
| 106 | + |
| 107 | +## Backward Compatibility Notes |
| 108 | + |
| 109 | +- The legacy global-merge behavior is replaced by per-domain composites. |
| 110 | + `active_translation` now reflects the default domain, not a cross-domain merge. |
| 111 | +- `install_global_translation` and `install_module_translation` still exist but |
| 112 | + are now order-agnostic and idempotent. |
| 113 | +- Builtins `_`, `__`, `ngettext` are installed at import-time. |
| 114 | + If you previously used `try: __; except NameError: install_global_translation(...)`, |
| 115 | + switch to `finalize_i18n` in the app entry point. |
| 116 | + |
| 117 | +## License |
| 118 | + |
| 119 | +This project is licensed under the terms of the LICENSE file. |
0 commit comments