Skip to content

Translation and localization

Translation stack: service adapter, manager, auto-translation providers, and string collection.

📦 TranslationService

TranslationService

Bases: TranslationServiceProtocol

Service wrapper around translation management.

change_language

change_language(language_name: str) -> bool

Switch application language using its display name.

change_language_by_code

change_language_by_code(language_code: str) -> bool

Switch application language using ISO code (e.g. "fr").

get_available_languages

get_available_languages() -> list[str]

Return available language codes.

get_current_language_name

get_current_language_name() -> str

Return the current language display name.

get_current_language_code

get_current_language_code() -> str

Return the current language code.

translate

translate(text: str) -> str

Translate a text using the active language context.

📦 TranslationManager

TranslationManager

TranslationManager()

Bases: QObject

Core translation engine for EzQt_App.

Handles .ts file loading, language switching, Qt translator installation and automatic widget retranslation on language change.

translation_count property

translation_count: int

Return the number of cached translations.

📡 Translation signal integration

TranslationManager exposes runtime signals used by the UI layer:

Signal Payload Purpose
languageChanged str (language code) Notify widgets/services that the active language changed
translation_started none Notify that async auto-translation queue became active
translation_finished none Notify that async auto-translation queue drained

AutoTranslator also emits low-level worker signals:

Signal Payload Purpose
translation_ready (original, translated) One async translation completed successfully
translation_error (original, error) One async translation failed

Example wiring:

from ezqt_app.services.translation import get_translation_manager

manager = get_translation_manager()

manager.languageChanged.connect(lambda code: print(f"Language -> {code}"))
manager.translation_started.connect(lambda: print("Auto-translation started"))
manager.translation_finished.connect(lambda: print("Auto-translation finished"))

📦 EzTranslator

EzTranslator is a QTranslator subclass installed permanently into Qt's translator chain. It intercepts every QCoreApplication.translate("EzQt_App", text) call made by any widget:

  • Cache hit — returns the translation immediately from the in-memory _ts_translations dictionary.
  • Identity — when the current language equals the source language (en by default), returns source_text as-is and persists the identity mapping to the source .ts file.
  • Cache miss — dispatches an async translation request (daemon thread) via AutoTranslator and returns None so Qt falls back to the source text for the current render cycle. When the translation arrives, LanguageChange is re-emitted and widgets retranslate automatically.

EzTranslator is installed once at TranslationManager init and is never removed, even during language switches (only the .qm-backed translator is swapped).

EzTranslator

EzTranslator(manager: TranslationManager)

Bases: QTranslator

Qt translator that intercepts unknown strings for auto-translation.

This translator is installed alongside the compiled .qm translator. Qt calls translate() on every installed translator in LIFO order until one returns a non-empty string. When source_text is not yet known, EzTranslator fires the async auto-translation pipeline and returns an empty string so Qt falls back to the source text for this render cycle. On the next LanguageChange (triggered once the translation arrives) the string is found in _ts_translations and returned immediately.

Parameters:

Name Type Description Default
manager TranslationManager

The owning :class:TranslationManager instance.

required

translate

translate(context: str, source_text: str, disambiguation: str | None = None, n: int = -1) -> str | None

Return the translation for source_text, or None if unknown.

Returning None signals Qt (via null QString) that this translator did not handle the string, so Qt continues querying the next installed translator and ultimately falls back to the source text.

Returning "" would mean "found, but translation is empty", which would incorrectly replace every untranslated string with an empty label.

When the string is absent from the in-memory cache and auto-translation is active, an async translation request is fired so the string will be available on the next LanguageChange cycle.

Parameters:

Name Type Description Default
context str

Qt translation context (only "EzQt_App" is handled).

required
source_text str

The English source string to translate.

required
disambiguation str | None

Optional disambiguation hint (unused).

None
n int

Plural form selector (unused).

-1

Returns:

Type Description
str | None

The translated string if cached, None otherwise.

📦 AutoTranslator

AutoTranslator

AutoTranslator(cache_dir: Path | None = None)

Bases: QObject

Automatic translation manager (disabled by default).

Each call to :meth:translate spawns a lightweight daemon thread that performs the HTTP round-trip in the background. Signals are emitted from that thread; Qt automatically delivers them via a queued connection to slots that live in the main thread, so the UI is never blocked.

A _pending set (guarded by _lock) deduplicates in-flight requests: if the same source string is requested while a thread for it is still running, no second thread is spawned.

translate

translate(text: str, source_lang: str, target_lang: str) -> str | None

Schedule an async translation and return None immediately.

If source_lang equals target_lang the method returns text immediately (identity translation — no HTTP request is made). If text is already cached the cached value is returned immediately. Otherwise a daemon thread is started and None is returned; the caller receives the result via :attr:translation_ready.

translate_sync

translate_sync(text: str, source_lang: str, target_lang: str) -> str | None

Translate text synchronously, blocking until a result is obtained.

Intended for use in CLI scripts, test helpers, and offline batch-processing tools that run outside the Qt event loop. Each provider call is a blocking HTTP request; the total wait time can reach len(providers) × timeout seconds if all providers fail.

Warning

Never call this method from the Qt main (UI) thread. Doing so blocks the event loop for the entire duration of the HTTP round-trips, freezing the application UI. For in-app translation use :meth:translate instead, which runs the request in a daemon thread.

Example::

# Appropriate usage — called from a CLI script, not from a Qt slot:
translator = get_auto_translator()
translator.enabled = True
result = translator.translate_sync("Hello", "en", "fr")
print(result)  # "Bonjour"

Parameters:

Name Type Description Default
text str

The source text to translate.

required
source_lang str

BCP-47 language code of the source text (e.g. "en").

required
target_lang str

BCP-47 language code of the desired output (e.g. "fr").

required

Returns:

Type Description
str | None

The translated string, or None if the translator is disabled or

str | None

all providers fail.

save_translation_to_ts

save_translation_to_ts(original: str, translated: str, target_lang: str, ts_file_path: Path) -> None

Append a single translation entry to a Qt Linguist .ts XML file.

⚠️ Provider response validation

External HTTP payloads from providers are schema-validated before use.

Provider Validation mode
GoogleTranslateProvider strict payload shape checks
MyMemoryProvider strict object schema checks
LibreTranslateProvider strict object schema checks

Invalid payloads are rejected and logged; translation returns None.

⚠️ Cache contract

translations.json is validated with a strict schema on read and write. Malformed entries are removed or rejected instead of being tolerated.

⚠️ Legacy fallback removed

LibreTranslateProvider no longer auto-falls back to an alternate host when the primary host returns an HTTP error.

📦 StringCollector

StringCollector

StringCollector(user_dir: Path | None = None)

String collector with language detection and task generation.

🔍 Providers

Available provider implementations:

  • GoogleTranslateProvider
  • MyMemoryProvider
  • LibreTranslateProvider

auto_translation_enabled in translation.config.yaml controls external provider calls. save_to_ts_files: true enables runtime persistence: every translated string is appended to the active .ts file and the .qm is recompiled via pyside6-lrelease. Widget registration and string collection are local workflow features and can remain enabled independently.