-
Notifications
You must be signed in to change notification settings - Fork 310
Support language settings #1513
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
67e995b
to
fa118d1
Compare
28e2263
to
c62f77f
Compare
When I rebase atop
Could you rebase and fix those please? |
Thanks. Just updated the PR! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, this is an exciting feature!!
Small comments below.
And a question: what should happen, and what happens, if the language setting in the database isn't one of the languages we offer in the settings page? I think this could happen if we remove a language setting (e.g. if we become more strict about when to offer a language in the UI) or we change the language tag for a set of translations. If we need to do a database migration, let's document that, or perhaps the app can just notice the inconsistency and unset the setting.
docs/translation.md
Outdated
ARB files for new languages are automatically created in Pull Requests generated | ||
[the update-translations GitHub workflow](/.github/workflows/update-translations.yml). | ||
However, this won't make them appear in settings. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nits:
- Is the first sentence missing a word? ("generated by the […]")
- I would say either "PRs" or "pull requests" (not capitalized if spelled out)
- Instead of "appear in settings", how about "appear in the in-app settings UI", for explicitness.
docs/translation.md
Outdated
When a language has a good percentage of strings translated, we should add it to | ||
settings. First, if the language tag is 'en-GB', [add a string](#add-string) | ||
named 'languageNameEnGb'. | ||
|
||
Then, update [localizations.dart](/lib/model/localizations.dart) to include | ||
the new language in `kSelfnamesByLocale` and `localeDisplayName`, following | ||
the instructions there. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think these two instructions would read more easily in a numbered or bulleted list.
docs/translation.md
Outdated
[the update-translations GitHub workflow](/.github/workflows/update-translations.yml). | ||
However, this won't make them appear in settings. | ||
|
||
When a language has a good percentage of strings translated, we should add it to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does Weblate show the percentage? Let's try to match what zulip-mobile uses for a threshold; I think it might be 5%?
lib/model/localizations.dart
Outdated
/// [ZulipLocalizations.supportedLocales] can include languages that only have | ||
/// a few strings translated. This map should only inlcude sufficiently | ||
/// translated locales from [ZulipLocalizations.supportedLocales]. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// [ZulipLocalizations.supportedLocales] can include languages that only have | |
/// a few strings translated. This map should only inlcude sufficiently | |
/// translated locales from [ZulipLocalizations.supportedLocales]. | |
/// This map only includes some of [ZulipLocalizations.supportedLocales]; | |
/// it includes languages that have substantially complete translations. | |
/// For what counts as substantially translated, see docs/translation.md. |
(and then make sure docs/translation.md answers the question)
lib/model/localizations.dart
Outdated
/// The map should be sorted by selfname, to help users find their | ||
/// language in the UI. When in doubt how to sort (like between different | ||
/// scripts, or in scripts you don't know), try to match the order found in | ||
/// other UIs, like for choosing a language in your phone's system settings. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does a map have an order? Maybe we should hard-code a List<(Locale, String)>
, which is definitely ordered, and compute kSelfnamesByLocale
from that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And indeed the commit message mentions making a list, but I guess the commit doesn't actually do that 🙂:
i18n: Maintain a list of supported languages with metadata
lib/model/localizations.dart
Outdated
case 'en': | ||
return languageEn; | ||
case 'pl': | ||
return languagePl; | ||
case 'ru': | ||
return languageRu; | ||
case 'uk': | ||
return languageUk; | ||
default: | ||
throw ArgumentError.value(locale, 'locale'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah (following from my comment above): or we have a list where each entry contains the language tag, Locale
, and languageEn
/etc.? And from that list, we generate kSelfnamesByLocale
and a map to support this localeDisplayName
method?
lib/model/database.dart
Outdated
// TODO(upstream): support Locale.fromLanguageTag: | ||
// https://github.com/flutter/flutter/issues/143491 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's remember to add this to
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method looks like it's basically an implementation of the desired upstream feature — is that right?
In that case let's keep the TODO but it can instead read like:
// TODO(upstream): send this as a factory Locale.fromLanguageTag:
// https://github.com/flutter/flutter/issues/143491
and no need to add it to that umbrella list — the issue isn't really affecting us, it's just an opportunity to send something useful upstream 🙂
lib/model/database.dart
Outdated
/// the method implements a subset of this EBNF grammar. | ||
// TODO(upstream): support Locale.fromLanguageTag: | ||
// https://github.com/flutter/flutter/issues/143491 | ||
Locale _fromLanguageTag(String languageTag) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add a comment saying how you produced this implementation? I'm not sure if it was written from scratch looking at a spec, or if it's a simplified version of something in Locale.fromLanguageTag
, etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was written from scratch to support parsing Unicode Language Identifier, whose grammar is defined here:
https://www.unicode.org/reports/tr35/#Unicode_language_identifier
How about this:
/// Parse a Unicode BCP 47 Language Identifier into [Locale].
///
/// Throw when it fails to convert [languageTag] into a [Locale].
///
/// This supports parsing a Unicode Language Identifier returned from
/// [Locale.toLanguageTag].
///
/// This implementation refers to a part of
/// [this EBNF grammar](https://www.unicode.org/reports/tr35/#Unicode_language_identifier),
/// assuming the identifier is valid without
/// [unicode_variant_subtag](https://www.unicode.org/reports/tr35/#unicode_variant_subtag).
///
/// This doesn't check if the [languageTag] is a valid identifier, (i.e., when
/// this returns without errors, the identifier is not necessarily
/// syntactically well-formed or valid).
lib/widgets/settings.dart
Outdated
@@ -17,17 +21,28 @@ class SettingsPage extends StatelessWidget { | |||
@override | |||
Widget build(BuildContext context) { | |||
final zulipLocalizations = ZulipLocalizations.of(context); | |||
|
|||
Widget? subtitle; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As a variable in SettingsPage.build
, this looks like it should have a more specific name—it's not the page's subtitle, but the subtitle for something on a specific item in the page.
Yeah, that's a good point. In case of removal without replacement, we will just assume the language is not set, until the user chooses one from the settings page — that's the scenario the test "handle unsupported (but valid) locale stored in database" covers. When we replace a language tag with another, e.g. replacing I'm not sure how common it is going to be for us to repurpose a language tag like |
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.
An alternative to implementing _fromLanguageTag is using the LocaleParser from package:intl/locale.dart. In this case though, the values to parse always come from `toLanguageTag`, so we can utilize that knowledge to get away with a more simple implementation, that should also be handy upstream. The locale stored in the database is not guaranteed to be one of our supported value, especially if we need to rename locales in the future. See discussion: zulip#1513 (comment)
This doesn't really have user-facing changes yet, because they cannot set the language. In this case, ZulipApp.locale is null, and localization will follow system setting, like we did before this change. This behavior is an improvement compared to the legacy app, which just uses English (en) when language is not set.
Since there is no Figma design for the settings page yet, the design is kept simple while mostly matching zulip-mobile: we show both selfname and name of each available language option, and leave out the search funtionality. We don't allow unsetting the language once it is set, but that can easily change. Fixes: zulip#1139
Thanks for the review! I have updated the PR and left a TODO for migrations. I think the current behavior should be forward-compatible, and we can add migrations or locale-alias mappings later on, if needed. |
I expect it will never happen that we take a language tag which used to mean one thing, and later re-use the same tag to mean a different thing. That's because these tags follow an Internet-wide standard (https://en.wikipedia.org/wiki/IETF_language_tag); and although a migration to free up an old name is an option for our app's local database, it's not an option for such a distributed standard. So those standard tags will never reuse an old name with a new meaning. |
Screenshots
Since we don't have enough translated strings for RTL languages, the following screenshots are with Arabic added temporarily.
Notice that the selfnames are always shown in their own locale.
preview documentation change
Fixes: #1139