lang

package
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 6, 2026 License: BSD-3-Clause Imports: 20 Imported by: 0

Documentation

Overview

Package lang introduces a translation and localisation API for Bellui applications

Since 2.5

Index

Constants

This section is empty.

Variables

View Source
var (
	// L is a shortcut to localize a string, similar to the gettext "_" function.
	// More info available on the `Localize` function.
	L = Localize

	// N is a shortcut to localize a string with plural forms, similar to the ngettext function.
	// More info available on the `LocalizePlural` function.
	N = LocalizePlural

	// X is a shortcut to get the localization of a string with specified key, similar to pgettext.
	// More info available on the `LocalizeKey` function.
	X = LocalizeKey

	// XN is a shortcut to get the localization plural form of a string with specified key, similar to npgettext.
	// More info available on the `LocalizePluralKey` function.
	XN = LocalizePluralKey
)

Functions

func AddLocaleChangeListener

func AddLocaleChangeListener(fn func()) (remove func())

AddLocaleChangeListener registers fn to be called whenever the active locale changes — through SetLocale or a ResolveSystemLocale re-resolve that lands on a different locale. It returns a function that removes the listener again; callers should invoke it when the listener is no longer needed so it can be garbage collected.

This is the per-subscriber companion to the app-level RegisterLocaleChangeRefresh: it lets an individual value re-derive its locale-dependent content on a change, rather than relaying out every window. The canonical user is github.com/belluikit/bellui/data/binding.NewLocalizedString. fn runs on whichever goroutine calls SetLocale (documented to be the main one).

Since: 2.8

func AddTranslations

func AddTranslations(r bellui.Resource) error

AddTranslations allows an app to load a bundle of translations. The language that this relates to will be inferred from the resource name, for example "fr.json". The data should be in json format.

func AddTranslationsFS

func AddTranslationsFS(fs embed.FS, dir string) (retErr error)

AddTranslationsFS supports adding all translations in one calling using an `embed.FS` setup. The `dir` parameter specifies the name or path of the directory containing translation files inside this embedded filesystem. Each file should be a json file with the name following pattern [prefix.]lang.json.

func AddTranslationsForLocale

func AddTranslationsForLocale(data []byte, l bellui.Locale) error

AddTranslationsForLocale allows an app to load a bundle of translations for a specified locale. The data should be in json format.

func BindText

func BindText(key string, apply func(string), data ...any) (remove func())

BindText connects a translation key to a string setter so the text follows the active locale. apply is called immediately with the current translation of key (resolved via Localize, i.e. the same lookup as L) and again every time the active locale changes through SetLocale, until the returned remove func is called.

It is the binding-independent counterpart to github.com/belluikit/bellui/data/binding.NewLocalizedString: use it for text on surfaces that have no data binding, such as a button label, a window title or a menu item, so a live language switch updates them without a manual rebuild. (Widgets that accept a github.com/belluikit/bellui/data/binding.String — e.g. via NewLabelWithData — should prefer NewLocalizedString.)

Example — a button label that re-translates on a live language switch:

btn := widget.NewButton("", onCancel)
remove := lang.BindText("Cancel", btn.SetText)
// ...later, when the button is no longer used:
remove()

Example — a window title and a menu item:

lang.BindText("My Application", w.SetTitle)
lang.BindText("Open", func(s string) { item.Label = s; menu.Refresh() })

Any template data is forwarded to Localize on every (re-)evaluation, so pass values, not pre-formatted strings, for them to localize correctly.

apply runs on whichever goroutine calls SetLocale (documented to be the main one), matching the threading rules for updating widgets. Call remove when the target is gone so the subscription can be garbage collected; long-lived UI (a window title, a permanent toolbar button) may simply leave it for the app lifetime.

Since: 2.8

func Compare

func Compare(a, b string) int

Compare returns -1, 0 or +1 as a sorts before, equal to, or after b under the active locale (see CurrentLocale). This is the locale-aware replacement for comparing user-visible strings by code point, which mis-sorts most non-English text (for example German "äpfel" belongs with "a", and Swedish "ö" sorts after "z"). Use it directly as the body of a sort closure:

sort.Slice(names, func(i, j int) bool { return lang.Compare(names[i], names[j]) < 0 })

Compare is safe for concurrent use and stays in step with SetLocale. Callers sorting a large slice may prefer a reusable Collator from NewCollator.

Note: referencing collation links the golang.org/x/text/collate tables (~1.75 MB); binaries that never call into this file do not pay that cost.

Since: 2.8

func CompareFold

func CompareFold(a, b string) int

CompareFold is like Compare but case- and accent-insensitive, suited to user-facing search and matching (for example filtering a list as the user types). It is safe for concurrent use.

Since: 2.8

func CurrencySymbol

func CurrencySymbol(currencyCode string) string

CurrencySymbol returns the narrow symbol for an ISO 4217 currency code. For example, "USD" → "$", "EUR" → "€", "GBP" → "£".

Since: 2.7

func CurrentLocale

func CurrentLocale() bellui.Locale

CurrentLocale returns the active locale used by the translation and formatting helpers. Unlike SystemLocale, which always reports the operating system locale, this honours an app- or test-level override (see SetLocaleForTests); with no override active it reflects the system locale.

It is the same locale source that IsRTL (and therefore the default text base direction) consults, so text shaping, direction and formatting stay in step.

Since: 2.8

func DatePattern

func DatePattern() string

DatePattern returns the Go time layout string for the current locale's short date format (e.g. "01/02/2006" for en-US, "02.01.2006" for de-DE).

Since: 2.7

func DatePatternHint

func DatePatternHint() string

DatePatternHint returns a human-readable placeholder for the locale's date format (e.g. "MM/DD/YYYY" for en-US, "DD.MM.YYYY" for de-DE).

Since: 2.7

func DateTimePattern

func DateTimePattern() string

DateTimePattern returns the combined date+time layout for the locale.

Since: 2.7

func DateTimePatternHint

func DateTimePatternHint() string

DateTimePatternHint returns a human-readable placeholder for the combined date+time format (e.g. "MM/DD/YYYY H:MM AM/PM" or "DD.MM.YYYY HH:MM").

Since: 2.7

func DayHeaders

func DayHeaders() []string

DayHeaders returns 7 single-character (narrow) day abbreviations starting from the locale's week start day.

Since: 2.7

func DecimalSeparator

func DecimalSeparator() string

DecimalSeparator returns the locale's decimal separator (e.g., "." or ",").

Since: 2.7

func DefaultCurrency

func DefaultCurrency() string

DefaultCurrency returns the ISO 4217 currency code for the current locale's region (e.g. "USD" for en-US, "EUR" for de-DE, "JPY" for ja-JP).

Since: 2.7

func FormatCurrency

func FormatCurrency(amount float64, currencyCode string) string

FormatCurrency formats amount using the locale's number formatting and the appropriate currency symbol. currencyCode is an ISO 4217 code (e.g. "USD").

Since: 2.7

func FormatDate

func FormatDate(t time.Time) string

FormatDate formats t using the locale's short date pattern.

Since: 2.7

func FormatDateTime

func FormatDateTime(t time.Time) string

FormatDateTime formats t using the locale's combined date+time pattern.

Since: 2.7

func FormatDecimal

func FormatDecimal(v float64, scale int) string

FormatDecimal formats a float64 using the current locale's number formatting. If scale < 0, minimal fractional digits are used (trailing zeros removed).

Since: 2.7

func FormatDistance

func FormatDistance(meters float64) string

FormatDistance renders a distance given in **metres** using the current locale's measurement system, converting the magnitude and picking a size- appropriate unit — so callers don't hand-roll the km-vs-mi + conversion dance that FormatUnit alone leaves to them (#861). Metric locales get metres below 1 km and kilometres above; imperial locales (see PreferredLengthUnit) get feet below 1 mile and miles above. The converted value is rounded to two decimals.

Since: 2.7

func FormatList

func FormatList(items []string) string

FormatList joins items into a single string using the current locale's CLDR list pattern. "a, b, and c" is English-specific — the conjunction word, the separators and the Oxford comma are all per-locale data — so this must not be hand-assembled with a literal ", " and "and". For example the same three items render "a, b, and c" (en) but "a, b und c" (de).

The pattern strings live in the translation catalog under list.two / list.start / list.middle / list.end and follow the CLDR two/start/middle/end structure, so they react to lang.SetLocale like the other formatters. Items are inserted verbatim (already-final strings); no templating is applied to them.

Since: 2.7

func FormatMass

func FormatMass(grams float64) string

FormatMass renders a mass given in **grams** using the current locale's measurement system (#861): imperial locales (US/GB/LR/MM) get pounds; metric locales get grams below 1 kg and kilograms above. The converted value is rounded to two decimals.

Since: 2.7

func FormatMonthYear

func FormatMonthYear(t time.Time) string

FormatMonthYear returns a locale-formatted "Month Year" string.

Since: 2.7

func FormatTime

func FormatTime(hour, minute int) string

FormatTime formats a time-of-day using the locale's time pattern.

Since: 2.7

func FormatUnit

func FormatUnit(value float64, unit Unit) string

FormatUnit renders value with the given unit using the current locale's unit pattern and number formatting (so "1234.5" carries the locale's decimal and group separators, and the unit label/placement comes from the catalog). The label is **count-inflected** per the locale's CLDR plural rules — e.g. Polish UnitMile is "1 mila" / "2 mile" / "5 mil" — so the value drives which plural form of the label is chosen. The caller chooses the unit; use PreferredLengthUnit to pick km vs mi from the locale first.

Since: 2.7

func GroupSeparator

func GroupSeparator() string

GroupSeparator returns the locale's grouping/thousands separator (e.g., "," or "." or "\u00a0").

Since: 2.7

func IsRTL

func IsRTL() bool

IsRTL reports whether the active locale uses a right-to-left script (for example Arabic or Hebrew). It follows the same current-locale source as the other lang formatting helpers, so it honours SetLocaleForTests and any app-level locale override. It is the locale source used for github.com/belluikit/bellui.TextDirectionAuto.

Since: 2.8

func IsRTLDirection

func IsRTLDirection() bool

IsRTLDirection reports whether the ambient user-interface direction is right-to-left. It resolves the process-wide direction setting (github.com/belluikit/bellui.CurrentLayoutDirection): an explicit LTR/RTL override wins, while the default github.com/belluikit/bellui.DirectionAuto defers to the active locale via IsRTL. Layout mirroring, direction-aware text alignment and the default text base direction all consult this; it returns false (left-to-right) by default, keeping the non-RTL path unchanged.

An ambient per-subtree direction takes precedence over the global setting: a container.DirectionScope (or a direction-scoped window) installs one while it lays out and paints its subtree, so a left-to-right dialog can flow naturally inside a right-to-left application. When no scope is active the resolution is unchanged, so the common path stays a single global read.

Since: 2.8

func IsRTLLocale

func IsRTLLocale(l bellui.Locale) bool

IsRTLLocale reports whether the given locale uses a right-to-left script. The script is taken from the locale tag when present, otherwise inferred from the language (for example "ar" implies the Arabic script). Unknown or unparseable locales are treated as left-to-right.

Since: 2.8

func LSelect

func LSelect(keyPrefix, selector string, data ...any) string

LSelect resolves a message whose wording depends on a non-count selector such as grammatical gender or formality — the ICU MessageFormat "select" case that CLDR plural rules cannot express (e.g. "{name} updated her/his/their profile").

It is the "blessed" multi-key pattern: author one catalog key per variant, suffixed with the selector, plus a mandatory ".other" variant used as the fallback. LSelect looks up keyPrefix+"."+selector, falling back to keyPrefix+".other", and finally to keyPrefix itself. Templating and locale reactivity come from the underlying LocalizeKey machinery.

// catalog: profile_updated.female / .male / .other
lang.LSelect("profile_updated", gender, map[string]any{"Name": name})

Since: 2.7

func LSelectPlural

func LSelectPlural(keyPrefix, selector string, count int, data ...any) string

LSelectPlural composes a selector variant with CLDR plural forms: it picks the keyPrefix+"."+selector message (falling back to keyPrefix+".other") and then resolves the correct plural form within it using count. Each variant key is a plural message (one/other/…), so gender and plurality compose as key suffixes without an app-side switch.

// catalog: items_updated.female = {one, other}, .male = {…}, .other = {…}
lang.LSelectPlural("items_updated", gender, n, map[string]any{"Count": n})

Since: 2.7

func Localize

func Localize(in string, data ...any) string

Localize asks the translation engine to translate a string, this behaves like the gettext "_" function. The string can be templated and the template data can be passed as a struct with exported fields, or as a map of string keys to any suitable value.

func LocalizeKey

func LocalizeKey(key, fallback string, data ...any) string

LocalizeKey asks the translation engine for the translation with specific ID. If it cannot be found then the fallback will be used. The string can be templated and the template data can be passed as a struct with exported fields, or as a map of string keys to any suitable value.

func LocalizePlural

func LocalizePlural(in string, count int, data ...any) string

LocalizePlural asks the translation engine to translate a string from one of a number of plural forms. This behaves like the ngettext function, with the `count` parameter determining the plurality looked up. The string can be templated and the template data can be passed as a struct with exported fields, or as a map of string keys to any suitable value.

func LocalizePluralKey

func LocalizePluralKey(key, fallback string, count int, data ...any) string

LocalizePluralKey asks the translation engine for the translation with specific ID in plural form. This behaves like the npgettext function, with the `count` parameter determining the plurality looked up. If it cannot be found then the fallback will be used. The string can be templated and the template data can be passed as a struct with exported fields, or as a map of string keys to any suitable value.

func ParseDate

func ParseDate(s string) (time.Time, error)

ParseDate parses a locale-formatted date string. It is lenient with single-digit day/month values (e.g. "1/5/2025" matches "01/02/2006").

Since: 2.7

func ParseDateTime

func ParseDateTime(s string) (time.Time, error)

ParseDateTime parses a locale-formatted date+time string.

Since: 2.7

func ParseLocaleFloat

func ParseLocaleFloat(s string) (float64, error)

ParseLocaleFloat parses a locale-formatted number string back to float64. It strips group separators and normalizes the decimal separator to '.'.

Since: 2.7

func Prefers24HourClock

func Prefers24HourClock() bool

Prefers24HourClock returns true if the current locale conventionally uses 24-hour time notation. Most of the world uses 24-hour; the map lists the minority of regions that prefer 12-hour clocks.

Since: 2.7

func RegisterLocaleChangeRefresh

func RegisterLocaleChangeRefresh(fn func())

RegisterLocaleChangeRefresh registers the callback that SetLocale — and a ResolveSystemLocale re-resolve that lands on a different locale — invokes when the active locale changes, so the app can re-lay-out and repaint its running windows (re-translating text and re-applying locale-derived formatting and direction). It is called automatically by the app package; applications do not need to call it.

This mirrors github.com/belluikit/bellui.RegisterLayoutDirectionRefresh, which wires the analogous refresh for an explicit layout-direction change.

func ResolveAlign

func ResolveAlign(a bellui.TextAlign) bellui.TextAlign

ResolveAlign maps a logical text alignment to the concrete alignment for the active layout direction. Under a right-to-left layout (see IsRTLDirection) github.com/belluikit/bellui.TextAlignLeading and github.com/belluikit/bellui.TextAlignTrailing are swapped so that leading resolves to the right edge and trailing to the left; github.com/belluikit/bellui.TextAlignCenter is unaffected. Under the default left-to-right layout the alignment is returned unchanged.

Since: 2.8

func ResolveSystemLocale

func ResolveSystemLocale()

ResolveSystemLocale re-runs the operating-system locale lookup and applies the result to translations, formatting, collation and text direction. When an explicit SetLocale (or test) override is active it does nothing, and when the resolved locale actually changes it triggers the same running-app relayout and listener notifications as SetLocale.

It exists for the Android start sequence: this package's initial lookup runs during the shared library's global constructor, before the Java runtime is attached, so it fails and settles on the English fallback (#282). The toolkit calls this itself once the JVM context is available — at app creation, again on each foregrounding to pick up a system locale changed while the app was backgrounded (the process survives such a change, so package init does not run again), and on an in-place configuration change (an Android 13+ per-app language change reaches a foregrounded app as onConfigurationChanged with no lifecycle transition, #288). Applications do not need to call it. Like SetLocale it should be called on the main goroutine.

Since: 1.0

func SetLocale

func SetLocale(l bellui.Locale)

SetLocale overrides the active locale used for translations, date and number formatting, and text direction. Pass a BCP 47 locale tag such as "ar", "fr-FR" or "sr-Cyrl-RS"; pass an empty locale to revert to the system locale.

Translations resolve to the closest supported language, while the full tag (including region and script) is retained for formatting, CurrentLocale, IsRTL and text shaping. Unlike SystemLocale, subsequent calls to those helpers reflect this override.

A non-empty locale also pins the app against later system-locale resolution: neither ResolveSystemLocale — which the toolkit runs itself on Android once the Java runtime is available — nor the localizer rebuild that AddTranslations and its variants trigger will replace it (translations added afterwards are still picked up for the overridden locale). Passing an empty locale releases the pin and reverts to tracking the system locale.

When the active locale actually changes and an app is running, every running window is re-laid-out and repainted — re-applying locale-derived formatting and direction and re-rendering each widget's current text — the way a theme change is applied. This follows the same threading rules as bellui.Settings.SetTheme, so call it on the main goroutine. If no app is running the new locale simply takes effect for UI built or refreshed afterwards.

Note that the relayout repaints existing text but does not re-evaluate the Localize/L, LocalizeKey/X and plural calls that produced it: a widget built from a static lang.L("Cancel") keeps the string captured at build time. For text that should re-translate on a live language switch, drive it from github.com/belluikit/bellui/data/binding.NewLocalizedString (or another AddLocaleChangeListener subscriber) instead of a pre-resolved string.

Since: 2.8

func SetLocaleForTests

func SetLocaleForTests(t interface{ Cleanup(func()) }, locale string)

SetLocaleForTests overrides the current locale for testing purposes. It is exported so that widget tests in other packages can test locale-dependent behavior. It should not be called from production code.

The locale counts as an explicit override for the duration of the test, so neither a toolkit re-resolve of the system locale (ResolveSystemLocale) nor an AddTranslations call can replace it.

The previous locale — and the previous override state — is automatically restored via t.Cleanup when the test (or subtest) finishes, preventing locale state from leaking between tests.

func SystemLocale

func SystemLocale() bellui.Locale

SystemLocale returns the primary locale on the current system. This may refer to a language that Bellui does not have translations for.

func TimePattern

func TimePattern() string

TimePattern returns the Go time layout for the locale's time-of-day format. Returns "3:04 PM" for 12-hour locales or "15:04" for 24-hour locales.

Since: 2.7

func TimePatternHint

func TimePatternHint() string

TimePatternHint returns a human-readable placeholder for the time format (e.g. "HH:MM" for 24h or "H:MM AM/PM" for 12h).

Since: 2.7

func WeekStartDay

func WeekStartDay() time.Weekday

WeekStartDay returns the first day of the week for the current locale, based on CLDR region data.

Since: 2.7

func WeekdayOffset

func WeekdayOffset(firstOfMonth time.Time) int

WeekdayOffset returns how many empty cells precede day 1 in a calendar grid that starts on the locale's week start day.

Since: 2.7

Types

type Collator

type Collator struct {
	// contains filtered or unexported fields
}

Collator compares strings under a fixed locale and set of options. Create one with NewCollator when sorting many strings so the collator is reused rather than rebuilt per comparison.

A Collator captures the active locale at construction time and does not track later SetLocale changes; build a fresh one if the locale may have changed. Like the underlying golang.org/x/text/collate.Collator it is not safe for concurrent use — give each goroutine its own, or use the package-level Compare, which is goroutine-safe.

Since: 2.8

func NewCollator

func NewCollator(opts ...CollatorOption) *Collator

NewCollator returns a Collator for the active locale (see CurrentLocale) with the given options applied.

Since: 2.8

func (*Collator) Compare

func (c *Collator) Compare(a, b string) int

Compare returns -1, 0 or +1 as a sorts before, equal to, or after b under the collator's locale and options.

Since: 2.8

func (*Collator) SortStrings

func (c *Collator) SortStrings(s []string)

SortStrings sorts s in place under the collator's locale and options.

Since: 2.8

type CollatorOption

type CollatorOption int

CollatorOption tunes how a Collator (or CompareFold) weights differences between strings. The zero set of options gives the locale's default ordering: case- and accent-sensitive, with runs of digits compared lexically.

Since: 2.8

const (
	// CollateNumeric compares embedded runs of digits by numeric value rather
	// than code point, so "file2" sorts before "file10".
	CollateNumeric CollatorOption = iota

	// CollateIgnoreCase makes comparison case-insensitive ("a" == "A").
	CollateIgnoreCase

	// CollateIgnoreDiacritics ignores diacritical marks ("o" == "ö"). Combined
	// with CollateIgnoreCase this yields a loose, search-friendly matching.
	CollateIgnoreDiacritics
)

type Unit

type Unit int

Unit identifies a measurement unit that FormatUnit knows how to render with a locale-appropriate pattern. The set is deliberately small — the handful of length and mass units a UI typically needs — and is extended on demand rather than mirroring the full CLDR unit grid.

Since: 2.7

const (
	UnitMeter Unit = iota
	UnitKilometer
	UnitMile
	UnitFoot
	UnitGram
	UnitKilogram
	UnitPound
)

Supported units. Length and mass only for now.

func PreferredLengthUnit

func PreferredLengthUnit() Unit

PreferredLengthUnit reports the road-distance length unit the current locale prefers — UnitMile for regions on the imperial/US measurement system (US, GB, LR, MM), UnitKilometer everywhere else (the CLDR default). Callers pass the result to FormatUnit after converting the magnitude themselves.

Since: 2.7

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL