packages

Localization

Read the user's preferred languages in order, each with its region, currency, separators, text direction, measurement system and temperature unit, plus the calendar type, IANA time zone, 24 hour clock and first weekday. Both lists always have at least one entry, so [0] is safe. Records are frozen dataclasses with snake_case fields, and Expo's camelCase keys work through locale["languageTag"]. No permissions.

  • pyxpo-localization
  • v0.1.0a1
  • Native: Swift + Kotlin
  • Like expo-localization
Language, region, currency and time zone on Android and iOS.
Language, region, currency and time zone on Android and iOS.

Install

Install the package into the same environment as pyxpo.

$pip install pyxpo-localization

Add localization to modules in your app config, then import it in your code.

pyproject.toml
[tool.pyxpo]
modules = ["localization"]
python
import pyxpo_localization as Localization

Needs Python 3.9+ and pyxpo>=0.1.0a2.

This module has native code. After you add it, run pyxpo run or pyxpo build again so it is compiled into the app. Hot reload alone is not enough.

Platforms

iOSiOS 15+. Everything. Temperature unit follows the user's setting on iOS 16+.
AndroidAPI 24+. Everything. Temperature unit follows the user's setting on Android 14+.
Desktop previewLanguage and region from LANG, time zone from the system. Currency, separators and the clock flag are None.
WebSame as the desktop.

Quick start

app/index.py
import pyxpo_localization as Localization
from pyxpo import Text, View, component


@component
def Settings():
    locale = Localization.use_locales()[0]
    cal = Localization.use_calendars()[0]
    return View(
        Text("Language: %s" % locale.language_tag),
        Text("Region: %s" % (locale.region_code or "unknown")),
        Text("Currency: %s %s" % (locale.currency_code or "?", locale.currency_symbol or "")),
        Text("Time zone: %s" % (cal.time_zone or "unknown")),
        Text("Clock: %s" % ("24 hour" if cal.uses_24_hour_clock else "12 hour")),
    )

Permissions

pyxpo adds these to Info.plist and AndroidManifest.xml for you when the module is listed.

iOSNone.
AndroidNone.

API reference

Functions

The values are read once when the app starts. If the user changes the language in Settings, the app sees it after a restart. Do not poll for a change.

get_locales()→ list[Locale]

The preferred languages, most preferred first. Never empty.

get_calendars()→ list[Calendar]

The user's calendar settings. Never empty.

use_locales()→ list[Locale]

The same list, for a component.

use_calendars()→ list[Calendar]

The same list, for a component.

Types

Both records are frozen dataclasses with to_dict() and camelCase item access, such as locale["languageTag"]. The camelCase key for the clock flag is uses24hourClock. Every function also has its expo spelling, such as Localization.getLocales.

Locale

KeyTypeDescription
language_tagstrBCP 47 tag, for example "en-IN". Always set.
language_codestr or NoneFor example "en".
language_script_codestr or NoneFor example "Hant". None when the tag has no script.
region_codestr or NoneThe region setting on iOS, the locale's region on Android.
language_region_codestr or NoneThe region inside the language tag.
currency_code, currency_symbolstr or NoneFor example "INR" and "₹". On iOS from the region setting.
language_currency_code, language_currency_symbolstr or NoneFrom the region of the language tag.
decimal_separatorstr or NoneFor example ".".
digit_grouping_separatorstr or NoneFor example ",".
text_directionstr or None"ltr" or "rtl".
measurement_systemstr or None"metric", "us" or "uk".
temperature_unitstr or None"celsius" or "fahrenheit".

Calendar

KeyTypeDescription
calendarstr or NoneCLDR name such as "gregory", as in JavaScript Intl.
time_zonestr or NoneIANA id such as "Asia/Kolkata".
uses_24_hour_clockbool or NoneNone on the desktop.
first_weekdayint or None1 is Sunday, 7 is Saturday, as in expo-localization.

Errors

KeyTypeDescription
ModuleNotLinkedErrorpyxpo.module_api"localization" is missing from modules. Add it and rebuild.
KeyErrorlocale["x"]A key that is not an Expo field name.

Examples

Pick a translation

Use the first locale for formatting and the whole list to choose a translation you have.

The locale fields on Android and iOS.
The locale fields on Android and iOS.
python
import pyxpo_localization as Localization

SUPPORTED = {"en", "hi", "de"}


def pick_language() -> str:
    locales = Localization.get_locales()
    return next((l.language_code for l in locales if l.language_code in SUPPORTED), "en")


def is_rtl() -> bool:
    return Localization.get_locales()[0].text_direction == "rtl"

Recipe: a price and a time in the user's style

This groups digits in threes. A region that groups differently, such as 12,34,567 in India, needs a number formatting library on top.

python
from datetime import datetime

import pyxpo_localization as Localization


def format_price(amount: float) -> str:
    locale = Localization.get_locales()[0]
    dec = locale.decimal_separator or "."
    grp = locale.digit_grouping_separator or ","
    whole, frac = ("%.2f" % amount).split(".")
    groups = []
    while len(whole) > 3:
        groups.insert(0, whole[-3:])
        whole = whole[:-3]
    groups.insert(0, whole)
    return "%s%s%s%s" % (locale.currency_symbol or "", grp.join(groups), dec, frac)


def format_time(moment: datetime) -> str:
    cal = Localization.get_calendars()[0]
    return moment.strftime("%H:%M") if cal.uses_24_hour_clock else moment.strftime("%I:%M %p")

Testing

Patch the getter, or build the records yourself. On the desktop test host, monkeypatch.setenv("LANG", "de_DE.UTF-8") also works.

python
import pyxpo_localization as Localization


def test_price_uses_the_user_symbol(monkeypatch):
    fake = Localization.Locale(language_tag="de-DE", currency_symbol="€",
                               decimal_separator=",", digit_grouping_separator=".")
    monkeypatch.setattr(Localization, "get_locales", lambda: [fake])
    assert Localization.get_locales()[0].currency_symbol == "€"

Good to know

  • No module options and no config plugin. The only setup is the modules entry.
  • These are the user's formats, not a currency conversion. The module does not know prices.
  • region_code and the language region can differ: the region setting and the language are separate settings.
  • On the desktop, en_US.UTF-8 in LANG becomes en-US, an unset or C locale gives en-US, and the time zone comes from TZ or /etc/localtime.
  • It does not translate text. Keep your own translation tables.