Source code for qtframework.i18n.contexts
"""Translation context helpers and lazy evaluation support.
This module provides advanced i18n features including:
- Translation contexts for disambiguation
- Lazy translation evaluation
- Message formatting with ICU-style patterns
"""
from __future__ import annotations
import contextlib
from functools import wraps
from typing import TYPE_CHECKING
from qtframework.i18n.babel_manager import get_i18n_manager
if TYPE_CHECKING:
from collections.abc import Callable
from babel.support import LazyProxy
[docs]
class TranslationContext:
"""Context for disambiguating translations.
Usage:
# Same word, different meanings
may_month = pgettext("month", "May")
may_permission = pgettext("permission", "May")
# Widget-specific context
save_button = pgettext("button", "Save")
save_menu = pgettext("menu", "Save")
"""
def __init__(self, context: str) -> None:
"""Initialize with a context string."""
self.context = context
self._manager = get_i18n_manager()
[docs]
def t(self, msgid: str, **kwargs) -> str:
"""Translate with this context."""
return self._manager.pgettext(self.context, msgid, **kwargs)
[docs]
def plural(self, singular: str, plural_form: str, n: int, **kwargs) -> str:
"""Translate plural with this context."""
return self._manager.npgettext(self.context, singular, plural_form, n, **kwargs)
[docs]
def lazy(self, msgid: str) -> LazyProxy:
"""Create lazy translation with this context."""
return self._manager.lazy_pgettext(self.context, msgid)
# Predefined contexts for common UI elements
[docs]
class UIContext:
"""Standard UI element contexts."""
button = TranslationContext("button")
menu = TranslationContext("menu")
tooltip = TranslationContext("tooltip")
label = TranslationContext("label")
title = TranslationContext("title")
placeholder = TranslationContext("placeholder")
error = TranslationContext("error")
warning = TranslationContext("warning")
info = TranslationContext("info")
success = TranslationContext("success")
dialog = TranslationContext("dialog")
tab = TranslationContext("tab")
header = TranslationContext("header")
footer = TranslationContext("footer")
[docs]
class LazyString:
"""Lazy string that delays translation until str() is called.
This is useful for module-level constants that need translation
but are defined before the locale is set.
Usage:
# At module level
ERROR_MESSAGE = LazyString("An error occurred")
# Later, when locale is set
print(str(ERROR_MESSAGE)) # Translates to current locale
"""
def __init__(self, msgid: str, context: str | None = None, **kwargs) -> None:
"""Initialize lazy string.
Args:
msgid: Message ID to translate
context: Optional context for disambiguation
**kwargs: Formatting parameters
"""
self.msgid = msgid
self.context = context
self.kwargs = kwargs
def __str__(self) -> str:
"""Evaluate translation when converted to string."""
manager = get_i18n_manager()
if self.context:
translated = manager.pgettext(self.context, self.msgid)
else:
translated = manager.t(self.msgid)
if self.kwargs:
with contextlib.suppress(KeyError, ValueError):
translated = translated.format(**self.kwargs)
return translated
def __repr__(self) -> str:
"""Return representation for debugging."""
return f"LazyString({self.msgid!r}, context={self.context!r})"
[docs]
def format(self, **kwargs) -> LazyString:
"""Create new LazyString with additional format parameters."""
new_kwargs = {**self.kwargs, **kwargs}
return LazyString(self.msgid, self.context, **new_kwargs)
[docs]
class LazyPlural:
"""Lazy plural string that delays translation until evaluation.
Example::
# At module level
ITEMS_COUNT = LazyPlural("{count} item", "{count} items")
# Later
print(ITEMS_COUNT.format(count=5)) # "5 items"
"""
def __init__(self, singular: str, plural: str, context: str | None = None) -> None:
"""Initialize lazy plural.
Args:
singular: Singular form
plural: Plural form
context: Optional context
"""
self.singular = singular
self.plural = plural
self.context = context
[docs]
def format(self, n: int, **kwargs) -> str:
"""Format with count and additional parameters.
Args:
n: Count for pluralization
**kwargs: Additional format parameters
Returns:
Translated and formatted string
"""
manager = get_i18n_manager()
if self.context:
translated = manager.npgettext(self.context, self.singular, self.plural, n)
else:
translated = manager.plural(self.singular, self.plural, n)
# Ensure count is in kwargs
if "count" not in kwargs:
kwargs["count"] = n
try:
return translated.format(**kwargs)
except (KeyError, ValueError):
return translated
def __repr__(self) -> str:
"""Return representation for debugging."""
return f"LazyPlural({self.singular!r}, {self.plural!r}, context={self.context!r})"
[docs]
def translatable_property(context: str | None = None):
"""Decorator for creating translatable class properties.
Example::
class MyWidget:
@translatable_property("tooltip")
def help_text(self):
return "Click here for help"
The property will automatically translate when accessed.
"""
def decorator(func: Callable) -> property:
"""Wrap function as a translatable property.
Args:
func: Function that returns the message ID
Returns:
Property that returns translated string
"""
@wraps(func)
def getter(self) -> str:
"""Get translated value.
Args:
self: Instance of the class
Returns:
Translated string
"""
msgid = func(self)
manager = get_i18n_manager()
if context:
return manager.pgettext(context, msgid)
return manager.t(msgid)
return property(getter)
return decorator
[docs]
class MessageFormatter:
"""Advanced message formatter supporting ICU-style patterns.
This provides more complex formatting than simple string.format(),
including gender, select cases, and nested patterns.
Usage:
formatter = MessageFormatter()
# Gender-aware messages
pattern = "{name} {gender, select, male {his} female {her} other {their}} profile"
result = formatter.format(pattern, name="Alex", gender="female")
# "Alex her profile"
# Plural with select
pattern = "{count, plural, =0 {no items} one {# item} other {# items}}"
result = formatter.format(pattern, count=5)
# "5 items"
"""
def __init__(self) -> None:
"""Initialize formatter."""
self._manager = get_i18n_manager()
[docs]
def format(self, pattern: str, **kwargs) -> str:
"""Format a pattern with parameters.
Args:
pattern: ICU-style message pattern
**kwargs: Parameters for formatting
Returns:
Formatted string
"""
# This is a simplified implementation
# For full ICU support, consider using py-icu library
result = pattern
# Handle select patterns
import re
select_pattern = r"\{(\w+),\s*select,\s*([^}]+)\}"
for match in re.finditer(select_pattern, result):
param_name = match.group(1)
cases = match.group(2)
if param_name in kwargs:
value = kwargs[param_name]
# Parse cases
case_dict = {}
for case_match in re.finditer(r"(\w+)\s*\{([^}]*)\}", cases):
case_key = case_match.group(1)
case_value = case_match.group(2)
case_dict[case_key] = case_value
# Get matching case or 'other'
replacement = case_dict.get(value, case_dict.get("other", ""))
result = result.replace(match.group(0), replacement)
# Handle plural patterns (simplified)
plural_pattern = r"\{(\w+),\s*plural,\s*([^}]+)\}"
for match in re.finditer(plural_pattern, result):
param_name = match.group(1)
cases = match.group(2)
if param_name in kwargs:
count = kwargs[param_name]
# Parse cases
case_dict = {}
for case_match in re.finditer(r"(=?\w+)\s*\{([^}]*)\}", cases):
case_key = case_match.group(1)
case_value = case_match.group(2)
# Replace # with count
case_value = case_value.replace("#", str(count))
case_dict[case_key] = case_value
# Determine which case to use
if f"={count}" in case_dict:
replacement = case_dict[f"={count}"]
elif count == 1 and "one" in case_dict:
replacement = case_dict["one"]
else:
replacement = case_dict.get("other", str(count))
result = result.replace(match.group(0), replacement)
# Handle simple placeholders
with contextlib.suppress(KeyError, ValueError):
result = result.format(**kwargs)
return result
# Convenience functions
[docs]
def lazy(msgid: str, context: str | None = None, **kwargs) -> LazyString:
"""Create a lazy translatable string."""
return LazyString(msgid, context, **kwargs)
[docs]
def lazy_plural(singular: str, plural: str, context: str | None = None) -> LazyPlural:
"""Create a lazy plural string."""
return LazyPlural(singular, plural, context)
# Context shortcuts
button = UIContext.button
menu = UIContext.menu
tooltip = UIContext.tooltip
label = UIContext.label
title = UIContext.title
error = UIContext.error
warning = UIContext.warning
info = UIContext.info
success = UIContext.success