///|
pub fn Catalog::from_json(
locale : String,
source : String,
) -> Catalog raise MessageError {
let json = @json.parse(source) catch {
error => raise InvalidCatalog(error.to_string())
}
let entries : Map[String, String] = @json.from_json(json) catch {
_ =>
raise InvalidCatalog(
"Catalog must be a JSON object whose values are strings.",
)
}
for key, template in entries {
let _ = parse_message(template) catch {
_ => raise InvalidCatalog("\{locale}/\{key}: invalid message template")
}
}
{ locale, entries }
}
///|
pub fn Catalog::locale(self : Catalog) -> String {
self.locale
}
///|
pub fn Catalog::get(self : Catalog, key : String) -> String? {
self.entries.get(key)
}
///|
pub fn Translator::new(
default_locale : String,
fallback_locale : String,
) -> Translator {
{
default_locale: canonicalize_locale_lossy(default_locale),
fallback_locale: canonicalize_locale_lossy(fallback_locale),
catalogs: Map([]),
}
}
///|
pub fn Translator::add_catalog(self : Translator, catalog : Catalog) -> Unit {
self.catalogs[canonicalize_locale_lossy(catalog.locale)] = catalog
}
///|
fn append_locale_chain(
output : Array[String],
seen : Map[String, Unit],
locale : String,
) -> Unit {
for candidate in locale_fallback_chain(locale) {
if !seen.contains(candidate) {
seen[candidate] = ()
output.push(candidate)
}
}
}
///|
fn Translator::lookup_chain(
self : Translator,
locale : String,
) -> Array[String] {
let output : Array[String] = []
let seen : Map[String, Unit] = Map([])
append_locale_chain(output, seen, locale)
append_locale_chain(output, seen, self.default_locale)
append_locale_chain(output, seen, self.fallback_locale)
output
}
///|
/// Resolve a key and preserve the locale of the catalog that supplied it.
pub fn Translator::resolve(
self : Translator,
key : String,
locale? : String,
) -> ResolvedMessage raise MessageError {
let requested = canonicalize_locale_lossy(
match locale {
Some(value) => value
None => self.default_locale
},
)
for candidate in self.lookup_chain(requested) {
match self.catalogs.get(candidate) {
Some(catalog) =>
match catalog.get(key) {
Some(template) =>
return {
key,
requested_locale: requested,
resolved_locale: canonicalize_locale_lossy(catalog.locale),
template,
used_fallback: requested !=
canonicalize_locale_lossy(catalog.locale),
}
None => ()
}
None => ()
}
}
raise MissingChoice("message key: \{key}")
}
///|
/// Look up and format a key, trying requested, default, then fallback locale.
pub fn Translator::translate(
self : Translator,
key : String,
arguments : Map[String, Argument],
locale? : String,
) -> String raise MessageError {
let requested = match locale {
Some(value) => value
None => self.default_locale
}
let resolved = self.resolve(key, locale=requested)
format(parse_message(resolved.template), arguments, resolved.resolved_locale)
}
///|
/// Resolve, format, and return locale provenance for a translation.
pub fn Translator::translate_detailed(
self : Translator,
key : String,
arguments : Map[String, Argument],
locale? : String,
) -> TranslationResult raise MessageError {
let resolved = match locale {
Some(value) => self.resolve(key, locale=value)
None => self.resolve(key)
}
let value = format(
parse_message(resolved.template),
arguments,
resolved.resolved_locale,
)
{
key,
requested_locale: resolved.requested_locale,
resolved_locale: resolved.resolved_locale,
value,
used_fallback: resolved.used_fallback,
}
}
///|
pub fn Translator::default_locale(self : Translator) -> String {
self.default_locale
}
///|
pub fn Translator::fallback_locale(self : Translator) -> String {
self.fallback_locale
}
///|
pub fn Translator::has_catalog(self : Translator, locale : String) -> Bool {
self.catalogs.contains(canonicalize_locale_lossy(locale))
}
///|
pub fn Translator::catalog_count(self : Translator) -> Int {
self.catalogs.length()
}
///|
pub fn Translator::available_locales(self : Translator) -> Array[String] {
let locales : Array[String] = []
for locale, _ in self.catalogs {
locales.push(locale)
}
locales.sort()
locales
}
///|
pub fn ResolvedMessage::key(self : ResolvedMessage) -> String {
self.key
}
///|
pub fn ResolvedMessage::requested_locale(self : ResolvedMessage) -> String {
self.requested_locale
}
///|
pub fn ResolvedMessage::resolved_locale(self : ResolvedMessage) -> String {
self.resolved_locale
}
///|
pub fn ResolvedMessage::template(self : ResolvedMessage) -> String {
self.template
}
///|
pub fn ResolvedMessage::used_fallback(self : ResolvedMessage) -> Bool {
self.used_fallback
}
///|
pub fn TranslationResult::key(self : TranslationResult) -> String {
self.key
}
///|
pub fn TranslationResult::requested_locale(self : TranslationResult) -> String {
self.requested_locale
}
///|
pub fn TranslationResult::resolved_locale(self : TranslationResult) -> String {
self.resolved_locale
}
///|
pub fn TranslationResult::value(self : TranslationResult) -> String {
self.value
}
///|
pub fn TranslationResult::used_fallback(self : TranslationResult) -> Bool {
self.used_fallback
}