flet_l10n - Localization for Flet Applications
flet_l10n is a Python package that provides localization support for Flet applications, enabling developers to create multilingual user interfaces with ease. Inspired by Flutter's l10n system, flet_l10n uses ARB (Application Resource Bundle) files and ICU MessageFormat for powerful, flexible translations.
Features
- Flutter-Compatible ARB Format - Use the same translation files as Flutter apps
- Full CLDR Plural Rules - Support for 50+ languages with proper pluralization (zero, one, two, few, many, other)
- Hot-Reload Support - Automatic translation reload during development
- Type-Safe API - Full type hints for excellent IDE support
- Lazy Loading - Translations are loaded only when needed
- Dual Integration Patterns - Global singleton or context-based providers with callback system
- CLI Tools - Project scaffolding, validation, and coverage reports
- Auto-Locale Detection - Automatically detect system locale on startup
- Compiled Pattern Caching - Optimized performance with LRU cache
- Update Callbacks - Automatic UI updates on locale change
Installation
pip install flet-l10n
Quick Start
1. Initialize Your Project
flet-l10n init
This creates:
l10n.yaml- Configuration filelocales/app_en.arb- Template ARB file with examples
2. Add More Locales
flet-l10n add-locale fr
flet-l10n add-locale es
3. Use in Your Flet App
Global Pattern (Simple)
import flet as ft
from flet_l10n import Localizations
from l10n_keys import L10nKeys # Assume you generate this file with "flet-l10n generate" command
# Initialize once
l10n = Localizations.from_config()
def main(page: ft.Page):
page.title = "flet_l10n Example"
# Create UI components
title = ft.Text(size=24, weight=ft.FontWeight.BOLD)
welcome = ft.Text(size=18)
items = ft.Text(size=16)
# Define update function
def update_ui():
title.value = l10n.t(L10nKeys.appTitle)
welcome.value = l10n.t(L10nKeys.welcome, name="Victoire")
items.value = l10n.plural(L10nKeys.itemCount, count=5)
page.update()
# Register callback for locale changes
l10n.on_locale_change(lambda _: update_ui())
# Language switcher
def change_language(e):
l10n.set_locale(e.control.value)
# update_ui() will be called automatically
dropdown = ft.Dropdown(
options=[
ft.dropdown.Option("en", "English en"),
ft.dropdown.Option("es", "Español es"),
ft.dropdown.Option("fr", "Français fr"),
],
value=l10n.current_locale,
on_select=change_language,
width=200,
)
# Initial update
update_ui()
page.add(
title,
welcome,
items,
ft.Divider(height=20),
dropdown,
)
ft.run(main)
Context-Based Pattern (Flet-Style)
"""Example demonstrating context-based provider pattern with proper UI updates."""
import flet as ft
from flet_l10n import LocalizationsProvider, use_localizations
from l10n_keys import L10nKeys # Assume you generate this file with "flet-l10n generate" command
def main(page: ft.Page):
page.title = "flet_l10n Provider Example"
page.theme_mode = ft.ThemeMode.LIGHT
page.padding = 40
# Initialize LocalizationsProvider
try:
provider = LocalizationsProvider(page, config_path="l10n.yaml")
except Exception:
# Fallback if no config
provider = LocalizationsProvider(
page,
arb_dir="locales",
default_locale="en",
hot_reload=True,
)
# Access localization through provider
l10n = use_localizations(page)
# Create UI components (imperative pattern)
title = ft.Text(size=30, weight=ft.FontWeight.BOLD)
welcome = ft.Text(size=20)
item_count = ft.Text(size=16)
current_locale_display = ft.Text(size=14, color=ft.Colors.BLUE_GREY_700)
# Locale dropdown
locale_dropdown = ft.Dropdown(
label="Select Language",
options=[
ft.dropdown.Option("en", "English"),
ft.dropdown.Option("es", "Español"),
ft.dropdown.Option("fr", "Français"),
],
width=250,
)
# Item count selector
count_slider = ft.Slider(
min=0,
max=10,
divisions=10,
value=3,
label="{value}",
width=300,
)
def update_ui():
"""Update all UI components with current translations.
This function is called automatically when locale changes.
"""
# Update all text components
title.value = l10n.t(L10nKeys.appTitle)
welcome.value = l10n.t(L10nKeys.welcome, name="Victoire")
item_count.value = l10n.plural(
L10nKeys.itemCount, count=int(count_slider.value)
)
current_locale_display.value = f"Current locale: {l10n.current_locale}"
locale_dropdown.value = l10n.current_locale
# Update page
page.update()
def change_locale(e):
"""Change locale when dropdown changes."""
l10n.set_locale(e.control.value)
# update_ui() will be called automatically via callback
def update_count(e):
"""Update item count when slider changes."""
item_count.value = l10n.plural(L10nKeys.itemCount, count=int(e.control.value))
page.update()
# Register update callback for automatic UI refresh on locale change
provider.add_update_callback(update_ui)
# Set event handlers
locale_dropdown.on_select = change_locale
count_slider.on_change = update_count
# Build UI
page.add(
ft.Column(
[
title,
ft.Divider(height=20),
welcome,
ft.Container(height=20),
current_locale_display,
ft.Container(height=20),
locale_dropdown,
ft.Container(height=30),
ft.Text("Adjust item count:", size=14, weight=ft.FontWeight.BOLD),
count_slider,
item_count,
ft.Container(height=30),
ft.Container(
content=ft.Column(
[
ft.Text(
"How it works:", size=14, weight=ft.FontWeight.BOLD
),
ft.Text(
"Change language → All components update automatically",
size=12,
),
ft.Text(
"Uses callback pattern for imperative updates",
size=12,
),
ft.Text(
"No need to manually refresh each component", size=12
),
],
spacing=5,
),
bgcolor=ft.Colors.BLUE_50,
padding=15,
border_radius=10,
),
],
horizontal_alignment=ft.CrossAxisAlignment.START,
spacing=10,
)
)
# Initial UI update
update_ui()
if __name__ == "__main__":
ft.run(main)
Important: With the context-based pattern, you must:
- Register an
update_ui()callback withprovider.add_update_callback() - This callback will be called automatically when locale changes
- Update all component values in the callback
See examples/ for complete working examples with both imperative and rebuild patterns.
Configuration (l10n.yaml)
arb-dir: locales
default-locale: en
fallback-locale: en
template-arb-file: app_en.arb
hot-reload: true
supported-locales:
- en
- es
- fr
ARB File Format
ARB files are JSON files with translation strings and metadata:
{
"@@locale": "en",
"appTitle": "My Application",
"@appTitle": {
"description": "The title of the application"
},
"welcome": "Welcome, {name}!",
"@welcome": {
"description": "Welcome message",
"placeholders": {
"name": {
"type": "String",
"example": "Victoire"
}
}
},
"itemCount": "{count, plural, =0{No items} one{One item} other{{count} items}}",
"@itemCount": {
"description": "Item count with pluralization",
"placeholders": {
"count": {
"type": "int"
}
}
},
"gender": "{gender, select, male{He} female{She} other{They}} likes this",
"@gender": {
"description": "Gender-based selection"
}
}
ICU MessageFormat Examples
Simple Placeholders
{
"greeting": "Hello, {name}!"
}
l10n.t("greeting", name="Alice") # "Hello, Alice!"
or
l10n.translate(L10nKeys.greeting, name="Alice") # After generating l10n_keys.py file with "flet-l10n generate" command
Pluralization (CLDR Rules)
{
"messages": "{count, plural, =0{No messages} one{One message} other{{count} messages}}"
}
l10n.plural("messages", count=0) # "No messages"
l10n.plural("messages", count=1) # "One message"
l10n.plural("messages", count=5) # "5 messages"
or
# After generating l10n_keys.py file with "flet-l10n generate" command
l10n.plural(L10nKeys.messages, count=0) # "No messages"
l10n.plural(L10nKeys.messages, count=1) # "One message"
l10n.plural(L10nKeys.messages, count=5) # "5 messages"
Select (Choice)
{
"preference": "{choice, select, coffee{I like coffee} tea{I like tea} other{I like water}}"
}
l10n.t("preference", choice="coffee") # "I like coffee"
or
# After generating l10n_keys.py file with "flet-l10n generate" command
l10n.t(L10nKeys.preference, choice="coffee") # "I like coffee"
CLI Commands
Initialize Project
flet-l10n init
flet-l10n init --arb-dir translations --default-locale fr
Add New Locale
flet-l10n add-locale es
flet-l10n add-locale de --from-template locales/app_en.arb
Validate ARB Files
flet-l10n validate
flet-l10n validate --arb-dir locales
Show Translation Coverage
flet-l10n coverage
Output:
Translation Coverage
┏━━━━━━━━┳━━━━━━┳━━━━━━━━━━┳━━━━━━━━━┓
┃ Locale ┃ Keys ┃ Coverage ┃ Missing ┃
┡━━━━━━━━╇━━━━━━╇━━━━━━━━━━╇━━━━━━━━━┩
│ en │ 3 │ 100.0% │ - │
│ es │ 3 │ 100.0% │ - │
│ fr │ 3 │ 100.0% │ - │
└────────┴──────┴──────────┴─────────┘
Total keys: 3
Watch for Changes (Development)
flet-l10n watch
Generate Typed Keys Class
Generate a Python class with typed constants for all your translation keys. This enables IDE autocompletion and prevents typos:
flet-l10n generate
flet-l10n generate --output l10n_keys.py --class-name L10nKeys
This creates a file like:
class L10nKeys:
"""Typed keys for localization strings."""
# The title of the application
appTitle = "appTitle"
# Welcome message with user name
# Placeholders: name
welcome = "welcome"
# Display item count with pluralization
# Placeholders: count
itemCount = "itemCount"
Use it in your code:
from l10n_keys import L10nKeys
# IDE will autocomplete L10nKeys.appTitle, L10nKeys.welcome, etc.
page.title = l10n.t(L10nKeys.appTitle)
greeting = l10n.t(L10nKeys.welcome, name="Alice")
Benefits:
- IDE Autocompletion - Get suggestions for all available keys
- Type Safety - Catch typos at edit time, not runtime
- Documentation - Docstrings show descriptions and placeholders
- Refactoring Support - Rename keys with confidence
API Reference
Localizations Class
# Initialize from config
l10n = Localizations.from_config("l10n.yaml")
# Initialize manually
l10n = Localizations(
arb_dir="locales",
default_locale="en",
fallback_locale="en",
hot_reload=True
)
# Translate
l10n.t("key", placeholder="value")
l10n.translate("key", placeholder="value")
# Plural
l10n.plural("key", count=5)
# Change locale
l10n.set_locale("es")
# Get current locale
current = l10n.current_locale
# Check if key exists
exists = l10n.has_key("someKey")
# Get all keys
keys = l10n.get_all_keys()
# Reload translations
l10n.reload_translations()
# Enable/disable hot-reload
l10n.enable_hot_reload()
l10n.disable_hot_reload()
# Register locale change callback
l10n.on_locale_change(lambda locale: print(f"Changed to {locale}"))
Context-Based Provider
from flet_l10n import LocalizationsProvider, use_localizations
# Initialize provider
provider = LocalizationsProvider(page, arb_dir="locales")
# Access localization
l10n = use_localizations(page)
# Register update callback (REQUIRED for UI updates)
def update_ui():
# Update your components here
title.value = l10n.t("appTitle")
page.update()
provider.add_update_callback(update_ui)
# Change locale (update_ui will be called automatically)
l10n.set_locale("es")
# Remove callback when no longer needed
provider.remove_update_callback(update_ui)
Two Update Patterns:
-
Imperative (Recommended for complex UIs):
# Update specific components def update_ui(): title.value = l10n.t("appTitle") welcome.value = l10n.t("welcome", name="User") page.update()
-
Rebuild (Simpler for small apps):
# Rebuild entire UI def rebuild_ui(): page.clean() page.add(build_ui()) page.update()
Supported Languages (CLDR Plural Rules)
flet_l10n includes CLDR plural rules for 50+ languages including:
- Germanic: English, German, Dutch, Swedish, Danish, Norwegian
- Romance: French, Spanish, Italian, Portuguese, Romanian
- Slavic: Russian, Polish, Czech, Slovak, Ukrainian, Serbian
- Arabic: Full 6-form plurals (zero, one, two, few, many, other)
- Asian: Chinese, Japanese, Korean (no plural distinction)
- Others: Turkish, Finnish, Hungarian, Hebrew, Greek, and more
Understanding Component Updates with Flet
Why Callbacks Are Needed
In Flet, UI components don't automatically re-evaluate their values when data changes. When you change the locale, your components (Text, Button, etc.) still have their old values. There are two approaches:
1. Imperative Pattern (Recommended)
Explicitly update each component's value:
def update_ui():
"""Called automatically when locale changes."""
title.value = l10n.t("appTitle")
welcome.value = l10n.t("welcome", name="User")
page.update()
provider.add_update_callback(update_ui)
Advantages:
- ✅ Efficient - only updates necessary components
- ✅ Fine control - update only what changed
- ✅ Best for complex UIs with many components
Example: See examples/provider_example.py
2. Rebuild Pattern (Simpler)
Reconstruct the entire UI:
def rebuild_ui():
"""Rebuild entire page on locale change."""
page.clean()
page.add(build_ui())
page.update()
provider.add_update_callback(rebuild_ui)
Advantages:
- ✅ Simple - no manual component tracking
- ✅ Less code - one function rebuilds everything
- ✅ Good for small/simple apps
Disadvantages:
- ⚠️ Less efficient - recreates all components
- ⚠️ Loses component state (scroll position, input values, etc.)
Example: See examples/provider_rebuild_example.py
Complete Working Example
import flet as ft
from flet_l10n import LocalizationsProvider, use_localizations
def main(page: ft.Page):
page.title = "Language Switcher"
page.theme_mode = ft.ThemeMode.LIGHT
# Initialize provider
provider = LocalizationsProvider(page, config_path="l10n.yaml")
l10n = use_localizations(page)
# Create components
title = ft.Text(size=32, weight=ft.FontWeight.BOLD)
welcome = ft.Text(size=20)
item_slider = ft.Slider(min=0, max=10, value=3, divisions=10, label="{value}")
item_count = ft.Text(size=16)
# Update function
def update_ui():
title.value = l10n.t("appTitle")
welcome.value = l10n.t("welcome", name="World")
item_count.value = l10n.plural("itemCount", count=int(item_slider.value))
dropdown.value = l10n.current_locale
page.update()
# Register callback
provider.add_update_callback(update_ui)
# Slider change handler
def on_slider_change(e):
item_count.value = l10n.plural("itemCount", count=int(e.control.value))
page.update()
item_slider.on_change = on_slider_change
# Language dropdown
dropdown = ft.Dropdown(
label="Select Language",
options=[
ft.dropdown.Option("en", "English 🇬🇧"),
ft.dropdown.Option("es", "Español 🇪🇸"),
ft.dropdown.Option("fr", "Français 🇫🇷"),
],
on_change=lambda e: l10n.set_locale(e.control.value),
width=250,
)
# Initial update
update_ui()
# Build UI
page.add(
ft.Container(
content=ft.Column([
title,
welcome,
ft.Divider(height=20),
ft.Text("Items:", size=14),
item_slider,
item_count,
ft.Divider(height=20),
dropdown,
], spacing=10),
padding=30,
)
)
ft.run(main)
Migration from Old Pattern
If you have code that doesn't use callbacks:
❌ Old (doesn't work):
def change_language(e):
l10n.set_locale(e.control.value)
page.update() # Components still have old values!
✅ New (works correctly):
# Register callback once
def update_ui():
title.value = l10n.t("appTitle")
welcome.value = l10n.t("welcome")
page.update()
provider.add_update_callback(update_ui)
# Now locale changes trigger update_ui automatically
def change_language(e):
l10n.set_locale(e.control.value)
Advanced Features
Locale Fallback Chain
# If locale "en-GB" is not found, flet_l10n will try:
# 1. en-GB (exact match)
# 2. en (language only)
# 3. Fallback locale (configured)
l10n.set_locale("en-GB")
Custom Locale Detection
import locale
# Auto-detect on init
l10n = Localizations(arb_dir="locales", auto_detect_locale=True)
# Manual detection
system_locale = locale.getdefaultlocale()[0]
l10n.set_locale(system_locale)
Hot-Reload with Callbacks
def on_translation_change(file_path):
print(f"Translations updated: {file_path}")
page.update()
l10n.enable_hot_reload(callback=on_translation_change)
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
MIT License - see LICENSE file for details.
Credits
Inspired by:
Support
Metadata
Release files for flet-l10n 0.1.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| flet_l10n-0.1.3.tar.gz | 35.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| flet_l10n-0.1.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 70.3 kB
Release files / flet_l10n-0.1.3.tar.gz
| Download URL | flet_l10n-0.1.3.tar.gz |
|---|---|
| Size | 35.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
fd3707920d363e06e25375f0bc64ebf30905ccb2378767f8cf8f59b843ebfd28
|
|
BLAKE2b-256 checksum How to use checksums |
011b87c0ea5ef25475787c5d3df09cb278600b0c79171f033c9a446e3da88b4b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.2
|
Release files / flet_l10n-0.1.3-py3-none-any.whl
| Download URL | flet_l10n-0.1.3-py3-none-any.whl |
|---|---|
| Size | 35.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9276c00f2bfc440ee6b362ea9e32d156147adf4524aa9f3cf79c884fad8f96ba
|
|
BLAKE2b-256 checksum How to use checksums |
c5a0ff90ca22c5a9d6f91d9a533b8271a1233ae22bd1f1fdc4fd9d75f9248b9c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.2
|