Complete translation system for Python applications with integrated GUI editor for managing multilingual content.
- β Locale-based translations stored in JSON files
- β
Support for both
tr()and_()syntax (gettext-compatible) - β
Automatic placeholder validation
{name},{count}, etc. - β Automatic placeholder preservation in translations
- β Multi-line string support
- β Fallback locale support
- β Dynamic font sizing to fit translated text in the same space
- β
Extract all
tr()and_()calls from Python source files - β Side-by-side editor: original text vs. translation
- β Visual placeholder validation with warnings
- β Search and filter functionality
- β Alphabetical sorting or original order
- β Undo/Redo support
- β Translation progress statistics
- β Auto-save with comprehensive error handling
pip install flet pillowfrom translator import TranslationSystem
# Initialize with locale
ts = TranslationSystem("de_DE")
_ = ts.tr # Create local alias (recommended)
# Simple translation
print(_("Hello World"))
# Translation with placeholders
greeting = _("Welcome {name}, you have {count} new messages")
print(greeting.format(name="Alice", count=5))
# Dynamic font sizing for UI elements
text = _("Settings", fontsize=20)
adjusted_size = ts.tr_size() # Returns font size to fit translated textfrom translator import TranslationSystem
ts = TranslationSystem()
ts.run_tr_extractor_ui()This opens a GUI where you can:
- Select your Python file
- Choose target locale
- Edit translations side-by-side
- Save to JSON automatically
Your project should follow this structure:
your_project/
βββ your_app.py # Your main application
βββ translator.py # TranslationSystem module
βββ assets/
βββ fonts/
β βββ Roboto-Regular.ttf # Optional for text measurement
βββ locales/
βββ your_app_de_DE.json # German translations
βββ your_app_en_US.json # English translations
βββ your_app_fr_FR.json # French translations
Naming Convention:
Translation files are named: {script_name}_{locale_code}.json
Example: If your script is demo.py, translations are stored as:
demo_de_DE.json(German)demo_en_US.json(English)demo_fr_FR.json(French)
Translation files are simple JSON dictionaries mapping original text to translations.
Example: demo_de_DE.json
{
"Hello World": "Hallo Welt",
"Welcome {name}": "Willkommen {name}",
"Settings": "Einstellungen",
"You have {count} new messages": "Sie haben {count} neue Nachrichten",
"Multi-line\nText example": "Mehrzeiliger\nText Beispiel"
}Important Rules:
- Preserve all placeholders like
{name},{count}in translations - Keep line breaks (
\n) if present in original - Use UTF-8 encoding (automatic with the editor)
Creates a new translation system instance.
# Initialize without locale (set later)
ts = TranslationSystem()
# Initialize with locale
ts = TranslationSystem("de_DE")Parameters:
locale_code(str, optional): Initial locale (e.g., "de_DE", "en_US", "fr_FR")
Returns: TranslationSystem instance
Main translation method. Translates text and prepares font size calculation for UI elements.
# Simple translation
translated = ts.tr("Hello World")
# Translation with font size specification
button_text = ts.tr("Click here", fontsize=16)
optimal_size = ts.tr_size() # Get calculated font sizeParameters:
text(str): Text to translatefontsize(int, default=20): Reference font size for measurement
Returns: Translated string with placeholders preserved
Notes:
- If translation is missing, returns original text
- Missing placeholders are automatically appended (with warning)
- Call
tr_size()after this to get adjusted font size
Alias for tr() - provides gettext-style syntax.
# Both are identical
text1 = ts.tr("Hello")
text2 = ts._("Hello")
# With local alias (recommended)
_ = ts.tr
print(_("Welcome"))Parameters: Same as tr()
Returns: Same as tr()
Set the active locale and load corresponding translations.
# Set locale
ts.set_locale("de_DE")
# Set locale with fallback
ts.set_locale("de_CH", fallback="de_DE")
# If Swiss German translation missing, falls back to standard GermanParameters:
locale_code(str): Locale to activate (e.g., "de_DE")fallback(str, optional): Fallback locale if translation missing
Returns: None
Effect: Loads translation file {script}_de_DE.json into cache
Returns the currently active locale code.
current = ts.get_locale()
print(f"Current locale: {current}") # "de_DE"Parameters: None
Returns: str - Current locale code (e.g., "de_DE") or None if not set
Lists all available locale codes by scanning the assets/locales/ directory.
available = ts.list_locales()
print(f"Available languages: {available}")
# ["de_DE", "en_GB", "en_US", "fr_FR"]Parameters: None
Returns: List[str] - Sorted list of available locale codes
Notes:
- Only lists locales for which JSON files exist
- Scans
assets/locales/{script}_{locale}.jsonfiles
Returns the calculated font size for the last translated text. This ensures translated text fits in the same space as the original.
# Translate with reference font size
text = ts.tr("Settings", fontsize=20)
# Get adjusted font size for translated text
new_size = ts.tr_size()
# Use in UI
button = Button(text=text, font_size=new_size)Parameters: None
Returns: int - Font size in points
How it works:
tr()measures original text at reference size- Measures translated text at same size
- Calculates scaling factor
tr_size()returns adjusted size (only scales down, never up)
Example:
Original: "Settings" (8 characters) at 20px
Translated: "Einstellungen" (13 characters) at 20px
tr_size() returns: ~12px (to fit same width)
Makes _() globally available by adding it to Python's builtins. This allows using _() without importing in every file.
ts = TranslationSystem("de_DE")
ts.install()
# Now _() is available everywhere without import
print(_("Hello"))
# Later, clean up
ts.uninstall()Parameters:
warn_on_override(bool, default=True): Show warning if_already exists in builtins
Returns: None
- Modifies global namespace (can conflict with other libraries)
- Not recommended for libraries/packages
- Recommended alternative: Use local alias
_ = ts.tr
Removes _() from builtins if it was installed.
ts.install()
print(_("Hello")) # Works
ts.uninstall()
print(_("Hello")) # NameError: name '_' is not definedParameters: None
Returns: None
Launches the Flet-based GUI editor for managing translations.
from translator import TranslationSystem
ts = TranslationSystem()
ts.run_tr_extractor_ui() # Opens GUI windowParameters: None
Returns: None (blocks until window is closed)
Features:
- File Selection: Pick any Python file to extract strings from
- Locale Selection: Choose target language
- Automatic Extraction: Finds all
tr()and_()calls - Side-by-Side Editor: Original (left) vs Translation (right)
- Placeholder Validation: Visual warnings for missing placeholders
- Search/Filter: Find specific strings quickly
- Sort Options: Alphabetically or original order
- Undo/Redo: Full editing history
- Statistics: Shows translation progress (e.g., "45/100 translated (45%)")
- Auto-Save: Saves to
assets/locales/{script}_{locale}.json
Workflow:
- Click "Select Python File"
- Choose locale from dropdown
- Edit translations in right column
- Missing placeholders highlighted in orange
- Click "Save" to export JSON
- Run your app to see translations
Requirements:
fletpackage installed- Write permissions in project directory
#!/usr/bin/env python3
import sys
from translator import TranslationSystem
# Initialize
ts = TranslationSystem()
ts.set_locale("de_DE")
_ = ts.tr
def main():
print(_("Welcome to the application!"))
print(_("Current locale: {locale}").format(locale=ts.get_locale()))
name = input(_("Enter your name: "))
print(_("Hello {name}!").format(name=name))
if __name__ == '__main__':
main()#!/usr/bin/env python3
import flet as ft
from translator import TranslationSystem
ts = TranslationSystem("de_DE")
_ = ts.tr
def main(page: ft.Page):
page.title = _("My Application")
# Define default font size
FONTSIZE = 20
# Configure theme
page.theme = ft.Theme(
font_family="Roboto",
text_theme=ft.TextTheme(
body_medium=ft.TextStyle(size=FONTSIZE)
)
)
# Translate and auto-size buttons
settings_text = ts.tr("Settings", FONTSIZE)
settings_size = ts.tr_size()
exit_text = ts.tr("Exit", FONTSIZE)
exit_size = ts.tr_size()
page.add(
ft.ElevatedButton(
settings_text,
style=ft.ButtonStyle(
text_style=ft.TextStyle(size=settings_size)
)
),
ft.ElevatedButton(
exit_text,
on_click=lambda _: page.window.close(),
style=ft.ButtonStyle(
text_style=ft.TextStyle(size=exit_size)
)
)
)
ft.app(target=main)from translator import TranslationSystem
ts = TranslationSystem()
_ = ts.tr
# Get available locales
locales = ts.list_locales()
print(f"Available: {locales}") # ['de_DE', 'en_US', 'fr_FR']
# Switch between locales
for locale in locales:
ts.set_locale(locale)
print(f"{locale}: {_('Hello World')}")
# Output:
# de_DE: Hallo Welt
# en_US: Hello World
# fr_FR: Bonjour le monde#!/usr/bin/env python3
import sys
from translator import TranslationSystem
ts = TranslationSystem()
# Uncomment these lines once to create translation files
# ts.run_tr_extractor_ui()
# sys.exit(0)
# After creating translations, use them
ts.set_locale("de_DE")
_ = ts.tr
def main():
print(_("This is my application"))
print(_("Processing {count} items...").format(count=42))
if __name__ == '__main__':
main()Workflow:
- Write code with
_()calls - Uncomment
run_tr_extractor_ui()andexit() - Run script β GUI opens
- Create translations for each locale
- Comment out GUI lines again
- Run normally with translations
# β
GOOD: Local alias
from translator import TranslationSystem
ts = TranslationSystem("de_DE")
_ = ts.tr
print(_("Hello"))Why?
- No global namespace pollution
- Works in modules and libraries
- No conflicts with other code
- Explicit and clear
# β AVOID: Global installation
ts = TranslationSystem("de_DE")
ts.install() # Modifies builtins
print(_("Hello"))# config.py
from translator import TranslationSystem
ts = TranslationSystem("de_DE")
_ = ts.tr
# main.py
from config import _, ts
print(_("Hello"))
print(f"Current locale: {ts.get_locale()}")# β
GOOD: Named placeholders (clear for translators)
message = _("Hello {name}, you have {count} messages")
print(message.format(name="Alice", count=5))
# β BAD: Positional placeholders (confusing context)
message = _("Hello {}, you have {} messages")
print(message.format("Alice", 5))Why named is better:
- Translators know what each placeholder means
- Different languages may need different word order
- Prevents translation errors
# β
GOOD: Complete sentences
print(_("Are you sure you want to delete this file?"))
# β BAD: Fragmented strings
print(_("Are you sure") + " " +
_("you want to delete") + " " +
_("this file?"))Why atomic is better:
- Context is preserved
- Translators can adjust entire sentence structure
- Different languages have different grammar rules
# β
GOOD: Context included
save_button = _("Save file")
save_money = _("Save 20% discount")
# β BAD: Same word, different meanings
save_button = _("Save") # Save file? Save money? Save settings?
save_money = _("Save") # Ambiguous for translators# β
GOOD: Natural line breaks
help_text = _("""
Welcome to the application!
This guide will help you understand
all features and get started quickly.
""")
# β
ALSO GOOD: Explicit line breaks
help_text = _("Line 1\nLine 2\nLine 3")
# β BAD: Very long single-line (hard to edit)
help_text = _("Welcome to the application! This is a comprehensive guide to help you understand all features...")# β
GOOD: Only translate user-facing text
filename = "data.txt"
print(_("Loading file: {filename}").format(filename=filename))
# β BAD: Translating variable names
filename = _("data.txt") # Don't translate filenames!
print(_("Loading file: {filename}").format(filename=filename))When a translation is missing, the system can fall back to another locale:
ts = TranslationSystem()
# Swiss German β Standard German β English
ts.set_locale("de_CH", fallback="de_DE")
# If "de_CH" translation missing, tries "de_DE"
# If "de_DE" also missing, returns original EnglishAllow users to change language at runtime:
import flet as ft
from translator import TranslationSystem
ts = TranslationSystem()
def main(page: ft.Page):
_ = ts.tr
def change_locale(e):
ts.set_locale(e.control.value)
update_ui()
def update_ui():
# Rebuild UI with new translations
page.clean()
page.add(
ft.Dropdown(
label=_("Language"),
options=[ft.dropdown.Option(loc) for loc in ts.list_locales()],
on_change=change_locale
),
ft.Text(_("Hello World"))
)
page.update()
update_ui()
ft.app(target=main)Ensure buttons don't grow when translated:
import flet as ft
from translator import TranslationSystem
ts = TranslationSystem("de_DE")
def create_fixed_button(text, fontsize=20):
"""Creates button with fixed width using adjusted font size."""
translated = ts.tr(text, fontsize)
adjusted_size = ts.tr_size()
return ft.ElevatedButton(
translated,
style=ft.ButtonStyle(
text_style=ft.TextStyle(size=adjusted_size)
)
)
def main(page: ft.Page):
page.add(
create_fixed_button("Settings"),
create_fixed_button("Configuration"),
create_fixed_button("Exit")
)
ft.app(target=main)Result:
- "Settings" (8 chars) β "Einstellungen" (13 chars)
- Font size automatically reduced to fit same space
- UI layout remains consistent across languages
from translator import TranslationSystem
ts = TranslationSystem()
ts.run_tr_extractor_ui()βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β tr() / _() Extractor & Editor β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β οΈ Warning: 3 entries with missing placeholders β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β [Locale: de_DE βΌ] [π Select Python File] β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β π File: demo_de_DE.json β
β π Found strings: 25 β
β β Newly added: 5 β
β π 18/25 translated (72.0%) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β [Search... ] [Sort: Original βΌ] β
β [βΆ Undo] [β· Redo] [πΎ Save] β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Original β Translation β
β Hello World β [Hallo Welt ] β
β Welcome {name} β [Willkommen {name} ] β
β Settings β [Einstellungen ] β
β You have {count} messages β [Sie haben Nachrichten] β β Orange (missing {count})
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
Saved successfully! β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
1. Select Python File
- Click "π Select Python File"
- Choose your
.pyfile - Editor extracts all
tr()and_()calls
2. Choose Locale
- Select language from dropdown (e.g., "de_DE")
- Loads existing translations or creates new file
3. Edit Translations
- Left column: Original text (read-only)
- Right column: Translation (editable)
- Type or paste translations
4. Validate Placeholders
- Missing placeholders β Orange background
- Warning banner shows count
- Fix manually or auto-append on save
5. Use Search/Filter
- Type in search box
- Filters by original OR translation
- Case-insensitive
6. Sort Entries
- "Original": Keep source code order
- "Alphabetically": Sort by original text
7. Undo/Redo
- Click "βΆ Undo" to revert
- Click "β· Redo" to restore
- Full history until save
8. Save
- Click "πΎ Save"
- Writes to
assets/locales/{script}_{locale}.json - Success/error message shown
The editor automatically detects missing placeholders:
Original: "Hello {name}, you have {count} messages"
Translation: "Hallo {name}" β Missing {count}
Result: Orange background + warning
On save, missing placeholders are automatically appended:
Before save: "Hallo {name}"
After save: "Hallo {name} {count}"
Recommendation: Fix manually for better quality.
Shows progress in real-time:
π 18/25 translated (72.0%)
Calculation:
- Total: Number of unique strings
- Translated: Strings where translation β original
- Percentage: (translated / total) Γ 100
The editor handles multi-line strings automatically:
# In your code
long_text = _("""
This is a long help text
that spans multiple lines.
""")
# In editor
Original: "This is a long help text\nthat spans multiple lines."
Translation: [Multi-line text field with word wrap]Search matches both original and translation:
Search: "message"
Matches:
- "You have new messages" β "Sie haben neue Nachrichten"
- "Message sent" β "Nachricht gesendet"
- "Error: Invalid message format" β "Fehler: UngΓΌltiges Nachrichtenformat"
Symptom: tr() returns original English text
Diagnostics:
print(f"Current locale: {ts.get_locale()}") # Should show "de_DE"
print(f"Available locales: {ts.list_locales()}") # Should include "de_DE"Solutions:
- Check locale is set:
ts.set_locale("de_DE") # Don't forget this!- Verify file exists:
ls assets/locales/
# Should show: your_script_de_DE.json- Check JSON syntax:
import json
with open("assets/locales/your_script_de_DE.json") as f:
data = json.load(f) # Should not throw error- Verify naming convention:
Script: demo.py
Locale file: demo_de_DE.json β Must match script name
Symptom: Orange background in editor
Example:
"Hello {name}": "Hallo" β Missing {name}Solution 1: Manual Fix (Recommended)
"Hello {name}": "Hallo {name}"Solution 2: Auto-Fix on Save
- Editor automatically appends missing placeholders
- Result:
"Hallo {name}" - But manual placement is better for quality
Symptom:
ModuleNotFoundError: No module named 'flet'
ModuleNotFoundError: No module named 'PIL'
Solution:
pip install flet pillowSymptom: tr_size() always returns default value (20)
Diagnostics:
print(ts.tr("Settings", 20))
print(ts.tr_size()) # Returns 20 instead of calculated valueSolution 1: Install Pillow
pip install pillowSolution 2: Add Font File
mkdir -p assets/fonts
# Copy Roboto-Regular.ttf to assets/fonts/Solution 3: Use Default Font If no custom font needed, Pillow will use default font automatically.
Symptom:
UnicodeEncodeError when saving translations
Solution: The editor uses UTF-8 automatically, but verify:
import json
data = {"Hello": "GrΓΌΓ Gott"}
with open("test.json", "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False)Symptom:
ts.set_locale("de_DE")
# Translations don't workDebug:
import os
app_name = "your_script"
locale = "de_DE"
expected = f"assets/locales/{app_name}_{locale}.json"
print(f"Looking for: {expected}")
print(f"Exists: {os.path.exists(expected)}")Solution:
- Create file using GUI editor
- Or create manually:
mkdir -p assets/locales
echo '{"Hello": "Hallo"}' > assets/locales/your_script_de_DE.json| Code | Language | Region |
|---|---|---|
de_DE |
German | Germany |
de_AT |
German | Austria |
de_CH |
German | Switzerland |
en_US |
English | United States |
en_GB |
English | United Kingdom |
fr_FR |
French | France |
es_ES |
Spanish | Spain |
it_IT |
Italian | Italy |
pt_BR |
Portuguese | Brazil |
ja_JP |
Japanese | Japan |
zh_CN |
Chinese | China (Simplified) |
zh_TW |
Chinese | Taiwan (Traditional) |
ru_RU |
Russian | Russia |
ar_SA |
Arabic | Saudi Arabia |
- β
set_locale()replaces oldtr_init()(more intuitive) - β
locale_codeparameter in__init__()for immediate setup - β
install()/uninstall()methods for global_()access - β
get_locale()to query current locale - β
list_locales()to discover available languages - β
_()as official alias fortr()
- β
Warning system for
install()to prevent conflicts - β Improved placeholder validation with visual feedback
- β Translation progress statistics
- β Enhanced error handling throughout
- β Fallback locale support
- β Better type hints for IDE support
- β Statistics display (translation completion %)
- β Better visual feedback (orange for errors)
- β File picker filters (.py files only)
- β Improved button states (disabled when appropriate)
- β Better error messages