Skip to content

Commit c130a80

Browse files
committed
i18n: Maintain a list of supported languages with metadata
This is similar to what we did in zulip-mobile: https://github.com/zulip/zulip-mobile/blob/91f5c3289/src/settings/languages.js The difference here being that the display names come from a live ZulipLocalizations instance, so we can't just hardcode the list with literal strings.
1 parent f8677ac commit c130a80

17 files changed

+289
-0
lines changed

assets/l10n/app_en.arb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -943,6 +943,22 @@
943943
"@openLinksWithInAppBrowser": {
944944
"description": "Label for toggling setting to open links with in-app browser"
945945
},
946+
"languageEn": "English",
947+
"@languageEn": {
948+
"description": "Label for the English language."
949+
},
950+
"languagePl": "Polish",
951+
"@languagePl": {
952+
"description": "Label for the Polish language."
953+
},
954+
"languageRu": "Russian",
955+
"@languageRu": {
956+
"description": "Label for the Russian language."
957+
},
958+
"languageUk": "Ukrainian",
959+
"@languageUk": {
960+
"description": "Label for the Ukrainian language."
961+
},
946962
"pollWidgetQuestionMissing": "No question.",
947963
"@pollWidgetQuestionMissing": {
948964
"description": "Text to display for a poll when the question is missing"

docs/translation.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ is working correctly.
2525

2626
## Adding new UI strings
2727

28+
<div id="add-string" />
29+
2830
### Adding a string to the translation database
2931

3032
To add a new string in the UI, start by
@@ -79,6 +81,26 @@ For example:
7981
`zulipLocalizations.subscribedToNChannels(store.subscriptions.length)`.
8082

8183

84+
## Adding a new language
85+
86+
ARB files for new languages are automatically created in pull requests generated
87+
by [the update-translations GitHub workflow](/.github/workflows/update-translations.yml).
88+
However, this won't make them in the in-app settings UI.
89+
90+
On [Weblate](https://hosted.weblate.org/projects/zulip/zulip-flutter/),
91+
we can check the percentage of strings translated in each language.
92+
We use this information to determine if we should start offerring the language
93+
in the in-app settings UI. For reference, on the web app, we offer a language
94+
when it is 5% translated. (Search for `percent_translated` in the server code.)
95+
96+
When a language has a good percentage of strings translated, follow these steps
97+
to add it:
98+
99+
- If the language tag is, for example, 'en-GB', [add a string](#add-string)
100+
named 'languageEnGb'.
101+
- Update [localizations.dart](/lib/model/localizations.dart) to include the new language in
102+
`languages`, following the instructions there.
103+
82104
## Hack to enforce locale (for testing, etc.)
83105

84106
For testing the app's behavior in different locales,

lib/generated/l10n/zulip_localizations.dart

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1407,6 +1407,30 @@ abstract class ZulipLocalizations {
14071407
/// **'Open links with in-app browser'**
14081408
String get openLinksWithInAppBrowser;
14091409

1410+
/// Label for the English language.
1411+
///
1412+
/// In en, this message translates to:
1413+
/// **'English'**
1414+
String get languageEn;
1415+
1416+
/// Label for the Polish language.
1417+
///
1418+
/// In en, this message translates to:
1419+
/// **'Polish'**
1420+
String get languagePl;
1421+
1422+
/// Label for the Russian language.
1423+
///
1424+
/// In en, this message translates to:
1425+
/// **'Russian'**
1426+
String get languageRu;
1427+
1428+
/// Label for the Ukrainian language.
1429+
///
1430+
/// In en, this message translates to:
1431+
/// **'Ukrainian'**
1432+
String get languageUk;
1433+
14101434
/// Text to display for a poll when the question is missing
14111435
///
14121436
/// In en, this message translates to:

lib/generated/l10n/zulip_localizations_ar.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -767,6 +767,18 @@ class ZulipLocalizationsAr extends ZulipLocalizations {
767767
@override
768768
String get openLinksWithInAppBrowser => 'Open links with in-app browser';
769769

770+
@override
771+
String get languageEn => 'English';
772+
773+
@override
774+
String get languagePl => 'Polish';
775+
776+
@override
777+
String get languageRu => 'Russian';
778+
779+
@override
780+
String get languageUk => 'Ukrainian';
781+
770782
@override
771783
String get pollWidgetQuestionMissing => 'No question.';
772784

lib/generated/l10n/zulip_localizations_de.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -767,6 +767,18 @@ class ZulipLocalizationsDe extends ZulipLocalizations {
767767
@override
768768
String get openLinksWithInAppBrowser => 'Open links with in-app browser';
769769

770+
@override
771+
String get languageEn => 'English';
772+
773+
@override
774+
String get languagePl => 'Polish';
775+
776+
@override
777+
String get languageRu => 'Russian';
778+
779+
@override
780+
String get languageUk => 'Ukrainian';
781+
770782
@override
771783
String get pollWidgetQuestionMissing => 'No question.';
772784

lib/generated/l10n/zulip_localizations_en.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -767,6 +767,18 @@ class ZulipLocalizationsEn extends ZulipLocalizations {
767767
@override
768768
String get openLinksWithInAppBrowser => 'Open links with in-app browser';
769769

770+
@override
771+
String get languageEn => 'English';
772+
773+
@override
774+
String get languagePl => 'Polish';
775+
776+
@override
777+
String get languageRu => 'Russian';
778+
779+
@override
780+
String get languageUk => 'Ukrainian';
781+
770782
@override
771783
String get pollWidgetQuestionMissing => 'No question.';
772784

lib/generated/l10n/zulip_localizations_it.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -773,6 +773,18 @@ class ZulipLocalizationsIt extends ZulipLocalizations {
773773
@override
774774
String get openLinksWithInAppBrowser => 'Open links with in-app browser';
775775

776+
@override
777+
String get languageEn => 'English';
778+
779+
@override
780+
String get languagePl => 'Polish';
781+
782+
@override
783+
String get languageRu => 'Russian';
784+
785+
@override
786+
String get languageUk => 'Ukrainian';
787+
776788
@override
777789
String get pollWidgetQuestionMissing => 'No question.';
778790

lib/generated/l10n/zulip_localizations_ja.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -767,6 +767,18 @@ class ZulipLocalizationsJa extends ZulipLocalizations {
767767
@override
768768
String get openLinksWithInAppBrowser => 'Open links with in-app browser';
769769

770+
@override
771+
String get languageEn => 'English';
772+
773+
@override
774+
String get languagePl => 'Polish';
775+
776+
@override
777+
String get languageRu => 'Russian';
778+
779+
@override
780+
String get languageUk => 'Ukrainian';
781+
770782
@override
771783
String get pollWidgetQuestionMissing => 'No question.';
772784

lib/generated/l10n/zulip_localizations_nb.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -767,6 +767,18 @@ class ZulipLocalizationsNb extends ZulipLocalizations {
767767
@override
768768
String get openLinksWithInAppBrowser => 'Open links with in-app browser';
769769

770+
@override
771+
String get languageEn => 'English';
772+
773+
@override
774+
String get languagePl => 'Polish';
775+
776+
@override
777+
String get languageRu => 'Russian';
778+
779+
@override
780+
String get languageUk => 'Ukrainian';
781+
770782
@override
771783
String get pollWidgetQuestionMissing => 'No question.';
772784

lib/generated/l10n/zulip_localizations_pl.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -777,6 +777,18 @@ class ZulipLocalizationsPl extends ZulipLocalizations {
777777
@override
778778
String get openLinksWithInAppBrowser => 'Otwieraj odnośniki w aplikacji';
779779

780+
@override
781+
String get languageEn => 'English';
782+
783+
@override
784+
String get languagePl => 'Polish';
785+
786+
@override
787+
String get languageRu => 'Russian';
788+
789+
@override
790+
String get languageUk => 'Ukrainian';
791+
780792
@override
781793
String get pollWidgetQuestionMissing => 'Brak pytania.';
782794

lib/generated/l10n/zulip_localizations_ru.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -781,6 +781,18 @@ class ZulipLocalizationsRu extends ZulipLocalizations {
781781
@override
782782
String get openLinksWithInAppBrowser => 'Открывать ссылки внутри приложения';
783783

784+
@override
785+
String get languageEn => 'English';
786+
787+
@override
788+
String get languagePl => 'Polish';
789+
790+
@override
791+
String get languageRu => 'Russian';
792+
793+
@override
794+
String get languageUk => 'Ukrainian';
795+
784796
@override
785797
String get pollWidgetQuestionMissing => 'Нет вопроса.';
786798

lib/generated/l10n/zulip_localizations_sk.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -769,6 +769,18 @@ class ZulipLocalizationsSk extends ZulipLocalizations {
769769
@override
770770
String get openLinksWithInAppBrowser => 'Open links with in-app browser';
771771

772+
@override
773+
String get languageEn => 'English';
774+
775+
@override
776+
String get languagePl => 'Polish';
777+
778+
@override
779+
String get languageRu => 'Russian';
780+
781+
@override
782+
String get languageUk => 'Ukrainian';
783+
772784
@override
773785
String get pollWidgetQuestionMissing => 'Bez otázky.';
774786

lib/generated/l10n/zulip_localizations_sl.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -789,6 +789,18 @@ class ZulipLocalizationsSl extends ZulipLocalizations {
789789
String get openLinksWithInAppBrowser =>
790790
'Odpri povezave v brskalniku znotraj aplikacije';
791791

792+
@override
793+
String get languageEn => 'English';
794+
795+
@override
796+
String get languagePl => 'Polish';
797+
798+
@override
799+
String get languageRu => 'Russian';
800+
801+
@override
802+
String get languageUk => 'Ukrainian';
803+
792804
@override
793805
String get pollWidgetQuestionMissing => 'Brez vprašanja.';
794806

lib/generated/l10n/zulip_localizations_uk.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -781,6 +781,18 @@ class ZulipLocalizationsUk extends ZulipLocalizations {
781781
String get openLinksWithInAppBrowser =>
782782
'Відкривати посилання за допомогою браузера додатку';
783783

784+
@override
785+
String get languageEn => 'English';
786+
787+
@override
788+
String get languagePl => 'Polish';
789+
790+
@override
791+
String get languageRu => 'Russian';
792+
793+
@override
794+
String get languageUk => 'Ukrainian';
795+
784796
@override
785797
String get pollWidgetQuestionMissing => 'Немає питання.';
786798

lib/generated/l10n/zulip_localizations_zh.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -767,6 +767,18 @@ class ZulipLocalizationsZh extends ZulipLocalizations {
767767
@override
768768
String get openLinksWithInAppBrowser => 'Open links with in-app browser';
769769

770+
@override
771+
String get languageEn => 'English';
772+
773+
@override
774+
String get languagePl => 'Polish';
775+
776+
@override
777+
String get languageRu => 'Russian';
778+
779+
@override
780+
String get languageUk => 'Ukrainian';
781+
770782
@override
771783
String get pollWidgetQuestionMissing => 'No question.';
772784

lib/model/localizations.dart

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import 'dart:ui';
2+
3+
import 'package:collection/collection.dart';
4+
15
import '../generated/l10n/zulip_localizations.dart';
26

37
abstract final class GlobalLocalizations {
@@ -15,3 +19,62 @@ abstract final class GlobalLocalizations {
1519
static ZulipLocalizations zulipLocalizations =
1620
lookupZulipLocalizations(ZulipLocalizations.supportedLocales.first);
1721
}
22+
23+
extension ZulipLocalizationsHelper on ZulipLocalizations {
24+
/// Returns a list of the locales we offer, with their display name in their
25+
/// own locale a.k.a. selfname, and the display name localized in this
26+
/// [ZulipLocalizations] instance.
27+
///
28+
/// This list only includes some of [ZulipLocalizations.supportedLocales];
29+
/// it includes languages that have substantially complete translations.
30+
/// For what counts as substantially translated, see docs/translation.md.
31+
///
32+
/// The list should be sorted by selfname, to help users find their
33+
/// language in the UI. When in doubt how to sort (like between different
34+
/// scripts, or in scripts you don't know), try to match the order found in
35+
/// other UIs, like for choosing a language in your phone's system settings.
36+
///
37+
/// For the values of selfname, consult Wikipedia:
38+
/// https://meta.wikimedia.org/wiki/List_of_Wikipedias
39+
/// https://en.wikipedia.org/wiki/Special:Preferences
40+
/// or better yet, Wikipedia's own mobile UIs. Wikipedia is a very
41+
/// conscientiously international and intercultural project with a lot of
42+
/// effort going into it by speakers of many languages, which makes it a
43+
/// useful gold standard for this.
44+
///
45+
/// This is adapted from:
46+
/// https://github.com/zulip/zulip-mobile/blob/91f5c3289/src/settings/languages.js
47+
///
48+
/// See also:
49+
/// * [kSelfnamesByLocale], a map for looking up the selfname of a locale,
50+
/// when its [ZulipLocalizations]-specific displayName is not needed.
51+
List<Language> languages() {
52+
return [
53+
(Locale('en'), 'English', languageEn),
54+
(Locale('pl'), 'Polski', languagePl),
55+
(Locale('ru'), 'Русский', languageRu),
56+
(Locale('uk'), 'Українська', languageUk),
57+
];
58+
}
59+
}
60+
61+
typedef Language = (Locale locale, String selfname, String displayName);
62+
63+
/// The map, of the locales we offer, to their display name in their own
64+
/// locale a.k.a. selfname.
65+
///
66+
/// See also:
67+
/// * [ZulipLocalizations.languages], similar helper that returns
68+
/// a list of locales with their localized display name in that
69+
/// [ZulipLocalizations] instance and their selfname.
70+
final kSelfnamesByLocale = UnmodifiableMapView(_selfnamesByLocale);
71+
final _selfnamesByLocale = {
72+
// While [ZulipLocalizations.languages]' result will be different depending
73+
// on the language setting, `locale` and `selfname` are the same for all
74+
// languages. This makes it fine to generate this map from
75+
// [GlobalLocalizations.zulipLocalizations],
76+
// despite it being a mutable static field.
77+
for (final (locale, selfname, _)
78+
in GlobalLocalizations.zulipLocalizations.languages())
79+
locale: selfname,
80+
};

0 commit comments

Comments
 (0)