// el-i18n — Localization for el-ui. // // Two-letter language tag + optional region: en, en-US, ar-EG, zh-Hant. // Bundles map keys to either a plain string or a plural-form map. // `t(key)` and `t_plural(key, count)` are the surface API. // ── Locale ─────────────────────────────────────────────────────────────────── let DIR_LTR: String = "ltr" let DIR_RTL: String = "rtl" type Locale { language: String // "en" region: String // "US" (may be empty) direction: String // "ltr" | "rtl" } fn locale_new(language: String, region: String) -> Locale { let dir: String = "ltr" if str_eq(language, "ar") { let dir = "rtl" } if str_eq(language, "he") { let dir = "rtl" } if str_eq(language, "fa") { let dir = "rtl" } if str_eq(language, "ur") { let dir = "rtl" } { "language": language, "region": region, "direction": dir } } fn locale_en_us() -> Locale { locale_new("en", "US") } fn locale_es_es() -> Locale { locale_new("es", "ES") } fn locale_zh_cn() -> Locale { locale_new("zh", "CN") } fn locale_ar_eg() -> Locale { locale_new("ar", "EG") } fn locale_ja_jp() -> Locale { locale_new("ja", "JP") } fn locale_tag(loc: Locale) -> String { if str_eq(loc.region, "") { return loc.language } loc.language + "-" + loc.region } fn is_rtl(loc: Locale) -> Bool { str_eq(loc.direction, "rtl") } // ── Plural forms (CLDR cardinal categories) ────────────────────────────────── // // Categories: zero, one, two, few, many, other. // Most languages only use one + other; Arabic uses all six. let PLURAL_ZERO: String = "zero" let PLURAL_ONE: String = "one" let PLURAL_TWO: String = "two" let PLURAL_FEW: String = "few" let PLURAL_MANY: String = "many" let PLURAL_OTHER: String = "other" fn plural_form(loc: Locale, n: Int) -> String { if str_eq(loc.language, "ar") { return plural_form_arabic(n) } if str_eq(loc.language, "ru") { return plural_form_russian(n) } if str_eq(loc.language, "pl") { return plural_form_polish(n) } if str_eq(loc.language, "ja") { return PLURAL_OTHER } if str_eq(loc.language, "zh") { return PLURAL_OTHER } if str_eq(loc.language, "ko") { return PLURAL_OTHER } // Default English-like rule if n == 1 { return PLURAL_ONE } PLURAL_OTHER } fn plural_form_arabic(n: Int) -> String { if n == 0 { return PLURAL_ZERO } if n == 1 { return PLURAL_ONE } if n == 2 { return PLURAL_TWO } let mod100: Int = n - ((n / 100) * 100) if mod100 >= 3 { if mod100 <= 10 { return PLURAL_FEW } } if mod100 >= 11 { if mod100 <= 99 { return PLURAL_MANY } } PLURAL_OTHER } fn plural_form_russian(n: Int) -> String { let mod10: Int = n - ((n / 10) * 10) let mod100: Int = n - ((n / 100) * 100) if mod10 == 1 { if mod100 == 11 { return PLURAL_MANY } return PLURAL_ONE } if mod10 >= 2 { if mod10 <= 4 { if mod100 >= 12 { if mod100 <= 14 { return PLURAL_MANY } } return PLURAL_FEW } } PLURAL_MANY } fn plural_form_polish(n: Int) -> String { if n == 1 { return PLURAL_ONE } let mod10: Int = n - ((n / 10) * 10) let mod100: Int = n - ((n / 100) * 100) if mod10 >= 2 { if mod10 <= 4 { if mod100 >= 12 { if mod100 <= 14 { return PLURAL_MANY } } return PLURAL_FEW } } PLURAL_MANY } // ── Translation bundle ────────────────────────────────────────────────────── // // Stored as a JSON map: key -> value | { one: "...", other: "..." } // `bundle_load_toml` parses a TOML file at load time (planned runtime fn). fn bundle_new() -> String { "{}" } fn bundle_insert(bundle: String, key: String, value: String) -> String { json_set(bundle, key, "\"" + value + "\"") } fn bundle_insert_plural(bundle: String, key: String, plural_map_json: String) -> String { json_set(bundle, key, plural_map_json) } fn bundle_load_toml(path: String) -> String { let raw: String = fs_read(path) toml_to_json(raw) } // ── LocaleContext + t/t_plural ────────────────────────────────────────────── type LocaleContext { locale: Locale bundle: String // JSON fallback_bundle: String } fn locale_context_new(loc: Locale, bundle: String) -> LocaleContext { { "locale": loc, "bundle": bundle, "fallback_bundle": "{}" } } fn t(ctx: LocaleContext, key: String) -> String { let v: String = json_get(ctx.bundle, key) if str_eq(v, "") { let v = json_get(ctx.fallback_bundle, key) } if str_eq(v, "") { return key } v } fn t_plural(ctx: LocaleContext, key: String, n: Int) -> String { let entry: String = json_get(ctx.bundle, key) if str_eq(entry, "") { return key } let form: String = plural_form(ctx.locale, n) let template: String = json_get(entry, form) if str_eq(template, "") { let template = json_get(entry, PLURAL_OTHER) } if str_eq(template, "") { return key } str_replace(template, "{n}", int_to_str(n)) } // ── Number / currency formatting ───────────────────────────────────────────── fn format_integer(loc: Locale, n: Int) -> String { // Group thousands by the locale's separator. Stub: en uses ',', most EU uses '.'. let sep: String = "," if str_eq(loc.language, "es") { let sep = "." } if str_eq(loc.language, "de") { let sep = "." } if str_eq(loc.language, "fr") { let sep = " " } int_with_separator(n, sep) } fn format_number(loc: Locale, n: Int, fraction_digits: Int) -> String { let dec_sep: String = "." if str_eq(loc.language, "es") { let dec_sep = "," } if str_eq(loc.language, "de") { let dec_sep = "," } if str_eq(loc.language, "fr") { let dec_sep = "," } format_integer(loc, n) + dec_sep + repeat_str("0", fraction_digits) } fn format_percent(loc: Locale, value_x100: Int) -> String { let body: String = int_to_str(value_x100 / 100) + "." + int_to_str(value_x100 - ((value_x100 / 100) * 100)) if str_eq(loc.language, "fr") { return body + " %" } body + "%" } fn format_currency(loc: Locale, amount_minor: Int, iso: String) -> String { // amount_minor is in the smallest unit (cents). Stub formatting only. let major: Int = amount_minor / 100 let minor: Int = amount_minor - (major * 100) let body: String = int_to_str(major) + "." + int_to_str(minor) if str_eq(iso, "USD") { return "$" + body } if str_eq(iso, "EUR") { return body + " €" } if str_eq(iso, "GBP") { return "£" + body } if str_eq(iso, "JPY") { return "¥" + int_to_str(amount_minor) } body + " " + iso } // ── Entry — smoke test ────────────────────────────────────────────────────── let loc: Locale = locale_en_us() let bundle: String = bundle_new() let bundle = bundle_insert(bundle, "profile.follow", "Follow") let ctx: LocaleContext = locale_context_new(loc, bundle) println("[el-i18n] " + t(ctx, "profile.follow"))