feat: add app.getPreferredSystemLanguages() API (#36035)

* feat: add app.getSystemLanguage() API

* Change the API to getPreferredSystemLanguages

* Fix test

* Clarify docs and add Linux impl

* Remove USE_GLIB

* Don't add C to list

* Remove examples since there's a lot of edge cases

* Fix lint

* Add examples

* Fix compile error

* Apply PR feedback

* Update the example
This commit is contained in:
Raymond Zhao 2022-11-09 07:50:43 -08:00 коммит произвёл GitHub
Родитель 8f5959aad2
Коммит 5fc3ed936e
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
6 изменённых файлов: 76 добавлений и 9 удалений

Просмотреть файл

@ -717,6 +717,8 @@ To set the locale, you'll want to use a command line switch at app startup, whic
**Note:** This API must be called after the `ready` event is emitted. **Note:** This API must be called after the `ready` event is emitted.
**Note:** To see example return values of this API compared to other locale and language APIs, see [`app.getPreferredSystemLanguages()`](#appgetpreferredsystemlanguages).
### `app.getLocaleCountryCode()` ### `app.getLocaleCountryCode()`
Returns `string` - User operating system's locale two-letter [ISO 3166](https://www.iso.org/iso-3166-country-codes.html) country code. The value is taken from native OS APIs. Returns `string` - User operating system's locale two-letter [ISO 3166](https://www.iso.org/iso-3166-country-codes.html) country code. The value is taken from native OS APIs.
@ -725,10 +727,42 @@ Returns `string` - User operating system's locale two-letter [ISO 3166](https://
### `app.getSystemLocale()` ### `app.getSystemLocale()`
Returns `string` - The current system locale. On Windows and Linux, it is fetched using Chromium's `i18n` library. On macOS, the `NSLocale` object is used instead. Returns `string` - The current system locale. On Windows and Linux, it is fetched using Chromium's `i18n` library. On macOS, `[NSLocale currentLocale]` is used instead. To get the user's current system language, which is not always the same as the locale, it is better to use [`app.getPreferredSystemLanguages()`](#appgetpreferredsystemlanguages).
Different operating systems also use the regional data differently:
* Windows 11 uses the regional format for numbers, dates, and times.
* macOS Monterey uses the region for formatting numbers, dates, times, and for selecting the currency symbol to use.
Therefore, this API can be used for purposes such as choosing a format for rendering dates and times in a calendar app, especially when the developer wants the format to be consistent with the OS.
**Note:** This API must be called after the `ready` event is emitted. **Note:** This API must be called after the `ready` event is emitted.
**Note:** To see example return values of this API compared to other locale and language APIs, see [`app.getPreferredSystemLanguages()`](#appgetpreferredsystemlanguages).
### `app.getPreferredSystemLanguages()`
Returns `string[]` - The user's preferred system languages from most preferred to least preferred, including the country codes if applicable. A user can modify and add to this list on Windows or macOS through the Language and Region settings.
The API uses `GlobalizationPreferences` (with a fallback to `GetSystemPreferredUILanguages`) on Windows, `\[NSLocale preferredLanguages\]` on macOS, and `g_get_language_names` on Linux.
This API can be used for purposes such as deciding what language to present the application in.
Here are some examples of return values of the various language and locale APIs with different configurations:
* For Windows, where the application locale is German, the regional format is Finnish (Finland), and the preferred system languages from most to least preferred are French (Canada), English (US), Simplified Chinese (China), Finnish, and Spanish (Latin America):
* `app.getLocale()` returns `'de'`
* `app.getSystemLocale()` returns `'fi-FI'`
* `app.getPreferredSystemLanguages()` returns `['fr-CA', 'en-US', 'zh-Hans-CN', 'fi', 'es-419']`
* On macOS, where the application locale is German, the region is Finland, and the preferred system languages from most to least preferred are French (Canada), English (US), Simplified Chinese, and Spanish (Latin America):
* `app.getLocale()` returns `'de'`
* `app.getSystemLocale()` returns `'fr-FI'`
* `app.getPreferredSystemLanguages()` returns `['fr-CA', 'en-US', 'zh-Hans-FI', 'es-419']`
Both the available languages and regions and the possible return values differ between the two operating systems.
As can be seen with the example above, on Windows, it is possible that a preferred system language has no country code, and that one of the preferred system languages corresponds with the language used for the regional format. On macOS, the region serves more as a default country code: the user doesn't need to have Finnish as a preferred language to use Finland as the region,and the country code `FI` is used as the country code for preferred system languages that do not have associated countries in the language name.
### `app.addRecentDocument(path)` _macOS_ _Windows_ ### `app.addRecentDocument(path)` _macOS_ _Windows_
* `path` string * `path` string

Просмотреть файл

@ -67,6 +67,7 @@
#include "shell/common/gin_converters/value_converter.h" #include "shell/common/gin_converters/value_converter.h"
#include "shell/common/gin_helper/dictionary.h" #include "shell/common/gin_helper/dictionary.h"
#include "shell/common/gin_helper/object_template_builder.h" #include "shell/common/gin_helper/object_template_builder.h"
#include "shell/common/language_util.h"
#include "shell/common/node_includes.h" #include "shell/common/node_includes.h"
#include "shell/common/options_switches.h" #include "shell/common/options_switches.h"
#include "shell/common/platform_util.h" #include "shell/common/platform_util.h"
@ -1803,6 +1804,7 @@ gin::ObjectTemplateBuilder App::GetObjectTemplateBuilder(v8::Isolate* isolate) {
.SetMethod("setAppLogsPath", &App::SetAppLogsPath) .SetMethod("setAppLogsPath", &App::SetAppLogsPath)
.SetMethod("setDesktopName", &App::SetDesktopName) .SetMethod("setDesktopName", &App::SetDesktopName)
.SetMethod("getLocale", &App::GetLocale) .SetMethod("getLocale", &App::GetLocale)
.SetMethod("getPreferredSystemLanguages", &GetPreferredLanguages)
.SetMethod("getSystemLocale", &App::GetSystemLocale) .SetMethod("getSystemLocale", &App::GetSystemLocale)
.SetMethod("getLocaleCountryCode", &App::GetLocaleCountryCode) .SetMethod("getLocaleCountryCode", &App::GetLocaleCountryCode)
#if BUILDFLAG(USE_NSS_CERTS) #if BUILDFLAG(USE_NSS_CERTS)

Просмотреть файл

@ -4,14 +4,31 @@
#include "shell/common/language_util.h" #include "shell/common/language_util.h"
#include "ui/base/l10n/l10n_util.h" #include <glib.h>
#include "base/check.h"
#include "base/i18n/rtl.h"
namespace electron { namespace electron {
std::vector<std::string> GetPreferredLanguages() { std::vector<std::string> GetPreferredLanguages() {
// Return empty as there's no API to use. You may be able to use std::vector<std::string> preferredLanguages;
// GetApplicationLocale() of a browser process.
return std::vector<std::string>{}; // Based on
// https://source.chromium.org/chromium/chromium/src/+/refs/tags/108.0.5329.0:ui/base/l10n/l10n_util.cc;l=543-554
// GLib implements correct environment variable parsing with
// the precedence order: LANGUAGE, LC_ALL, LC_MESSAGES and LANG.
const char* const* languages = g_get_language_names();
DCHECK(languages); // A valid pointer is guaranteed.
DCHECK(*languages); // At least one entry, "C", is guaranteed.
for (; *languages; ++languages) {
if (strcmp(*languages, "C") != 0) {
preferredLanguages.push_back(base::i18n::GetCanonicalLocale(*languages));
}
}
return preferredLanguages;
} }
} // namespace electron } // namespace electron

Просмотреть файл

@ -124,6 +124,19 @@ describe('app module', () => {
}); });
}); });
describe('app.getPreferredSystemLanguages()', () => {
ifit(process.platform !== 'linux')('should not be empty', () => {
expect(app.getPreferredSystemLanguages().length).to.not.equal(0);
});
ifit(process.platform === 'linux')('should be empty or contain C entry', () => {
const languages = app.getPreferredSystemLanguages();
if (languages.length) {
expect(languages).to.not.include('C');
}
});
});
describe('app.getLocaleCountryCode()', () => { describe('app.getLocaleCountryCode()', () => {
it('should be empty or have length of two', () => { it('should be empty or have length of two', () => {
const localeCountryCode = app.getLocaleCountryCode(); const localeCountryCode = app.getLocaleCountryCode();

Просмотреть файл

@ -375,6 +375,7 @@ describe('command line switches', () => {
describe('--lang switch', () => { describe('--lang switch', () => {
const currentLocale = app.getLocale(); const currentLocale = app.getLocale();
const currentSystemLocale = app.getSystemLocale(); const currentSystemLocale = app.getSystemLocale();
const currentPreferredLanguages = JSON.stringify(app.getPreferredSystemLanguages());
const testLocale = async (locale: string, result: string, printEnv: boolean = false) => { const testLocale = async (locale: string, result: string, printEnv: boolean = false) => {
const appPath = path.join(fixturesPath, 'api', 'locale-check'); const appPath = path.join(fixturesPath, 'api', 'locale-check');
const args = [appPath, `--set-lang=${locale}`]; const args = [appPath, `--set-lang=${locale}`];
@ -397,9 +398,9 @@ describe('command line switches', () => {
expect(output).to.equal(result); expect(output).to.equal(result);
}; };
it('should set the locale', async () => testLocale('fr', `fr|${currentSystemLocale}`)); it('should set the locale', async () => testLocale('fr', `fr|${currentSystemLocale}|${currentPreferredLanguages}`));
it('should set the locale with country code', async () => testLocale('zh-CN', `zh-CN|${currentSystemLocale}`)); it('should set the locale with country code', async () => testLocale('zh-CN', `zh-CN|${currentSystemLocale}|${currentPreferredLanguages}`));
it('should not set an invalid locale', async () => testLocale('asdfkl', `${currentLocale}|${currentSystemLocale}`)); it('should not set an invalid locale', async () => testLocale('asdfkl', `${currentLocale}|${currentSystemLocale}|${currentPreferredLanguages}`));
const lcAll = String(process.env.LC_ALL); const lcAll = String(process.env.LC_ALL);
ifit(process.platform === 'linux')('current process has a valid LC_ALL env', async () => { ifit(process.platform === 'linux')('current process has a valid LC_ALL env', async () => {

2
spec/fixtures/api/locale-check/main.js поставляемый
Просмотреть файл

@ -9,7 +9,7 @@ app.whenReady().then(() => {
if (process.argv[3] === '--print-env') { if (process.argv[3] === '--print-env') {
process.stdout.write(String(process.env.LC_ALL)); process.stdout.write(String(process.env.LC_ALL));
} else { } else {
process.stdout.write(`${app.getLocale()}|${app.getSystemLocale()}`); process.stdout.write(`${app.getLocale()}|${app.getSystemLocale()}|${JSON.stringify(app.getPreferredSystemLanguages())}`);
} }
process.stdout.end(); process.stdout.end();