/// The interface languages the client ships. Pure data: the settings page /// offers them, the loader reads their files, and the app tells Flutter which /// one is in use. library; /// One shipped language: the tag used in settings and in the asset path, and /// the id of its name in the locale files. A language is always named in its /// own words, so a reader who cannot read the current interface can still find /// theirs. typedef AppLocale = ({String tag, String labelId}); /// Offered in this order, English first and then alphabetically by endonym. const appLocales = [ (tag: 'en-US', labelId: 'language-en'), (tag: 'de', labelId: 'language-de'), (tag: 'es', labelId: 'language-es'), (tag: 'fr', labelId: 'language-fr'), (tag: 'he', labelId: 'language-he'), (tag: 'ja', labelId: 'language-ja'), (tag: 'pt', labelId: 'language-pt'), (tag: 'ru', labelId: 'language-ru'), ]; /// What the reader's language setting holds when they have not chosen one. const systemLanguage = 'system'; const fallbackLocale = 'en-US'; /// Written right to left, so the whole interface mirrors. const rtlLocales = {'he'}; bool isRightToLeft(String tag) => rtlLocales.contains(_languageOf(tag)); /// The shipped locale for [tag], or null when nothing matches. A tag matches /// on its language alone, so a device set to de-AT reads the German files. String? matchLocale(String tag) { final language = _languageOf(tag); for (final locale in appLocales) { if (locale.tag == tag) return locale.tag; } for (final locale in appLocales) { if (_languageOf(locale.tag) == language) return locale.tag; } return null; } /// The first of [tags] the client ships, for resolving the device's list of /// preferred languages. String resolveLocale(Iterable tags) { for (final tag in tags) { final match = matchLocale(tag); if (match != null) return match; } return fallbackLocale; } String _languageOf(String tag) => tag.split(RegExp('[-_]')).first.toLowerCase(); /// Which plural message a count needs, as a message id suffix. Zero is handled /// before this, so every language can say "none" in its own way. /// /// Only the languages the client ships are described (CLDR cardinal rules): /// Russian separates two to four from the rest, Hebrew has a form for two, /// French and Portuguese treat zero and one alike, and Japanese has one form. String pluralSuffix(String tag, int count) { final n = count.abs(); switch (_languageOf(tag)) { case 'ru': final mod10 = n % 10; final mod100 = n % 100; if (mod10 == 1 && mod100 != 11) return 'one'; if (mod10 >= 2 && mod10 <= 4 && !(mod100 >= 12 && mod100 <= 14)) { return 'few'; } return 'many'; case 'he': if (n == 1) return 'one'; if (n == 2) return 'two'; return 'many'; case 'fr': case 'pt': return n <= 1 ? 'one' : 'many'; case 'ja': return 'many'; default: return n == 1 ? 'one' : 'many'; } }