From fd49a10f1c02d4e44647c74d4799f191f8601693 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ky=E2=84=93e=20Hensel?= Date: Tue, 25 Mar 2025 00:27:18 +1100 Subject: [PATCH] consider language scripts when determining the locale to use (#10910) --- CHANGELOG.md | 2 ++ modules/core/localizer.js | 30 ++++++++++++++++++++---------- test/spec/core/localizer.js | 25 +++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 98a23acad..410b0db2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,7 @@ _Breaking developer changes, which may affect downstream projects or sites that * Make features clickable when "Full Fill" rendering style is selected * Fix calculation of access field placeholders for multi selections ([#9333]) #### :earth_asia: Localization +* Consider language scripts when determining the locale to use ([#10910], thanks [@k-yle]) #### :hourglass: Performance #### :mortar_board: Walkthrough / Help #### :rocket: Presets @@ -74,6 +75,7 @@ _Breaking developer changes, which may affect downstream projects or sites that [#10843]: https://github.com/openstreetmap/iD/pull/10843 [#10852]: https://github.com/openstreetmap/iD/issues/10852 [#10885]: https://github.com/openstreetmap/iD/issues/10885 +[#10910]: https://github.com/openstreetmap/iD/pull/10910 [id-tagging-schema#609]: https://github.com/openstreetmap/id-tagging-schema/issues/609 [@0xatulpatil]: https://github.com/0xatulpatil [@MohamedAli00949]: https://github.com/MohamedAli00949 diff --git a/modules/core/localizer.js b/modules/core/localizer.js index 1637c5d86..75616032e 100644 --- a/modules/core/localizer.js +++ b/modules/core/localizer.js @@ -108,11 +108,8 @@ export function coreLocalizer() { _dataLocales = results[1]; let indexes = results.slice(2); - let requestedLocales = (_preferredLocaleCodes || []) - .concat(utilDetect().browserLocales) // List of locales preferred by the browser in priority order. - .concat(['en']); // fallback to English since it's the only guaranteed complete language - _localeCodes = localesToUseFrom(requestedLocales); + _localeCodes = localizer.localesToUseFrom(_dataLocales); _localeCode = _localeCodes[0]; // Run iD in the highest-priority locale; the rest are fallbacks let loadStringsPromises = []; @@ -139,23 +136,36 @@ export function coreLocalizer() { }; // Returns the locales from `requestedLocales` supported by iD that we should use - function localesToUseFrom(requestedLocales) { - let supportedLocales = _dataLocales; + /** @param {{ [locale: string]: unknown }} supportedLocales */ + localizer.localesToUseFrom = (supportedLocales) => { + const requestedLocales = [ + ...(_preferredLocaleCodes || []), + ...utilDetect().browserLocales, // List of locales preferred by the browser in priority order. + 'en', // fallback to English since it's the only guaranteed complete language + ]; + /** @type {string[]} */ let toUse = []; - for (let i in requestedLocales) { - let locale = requestedLocales[i]; + for (const locale of requestedLocales) { if (supportedLocales[locale]) toUse.push(locale); - if (locale.includes('-')) { + if ('Intl' in window && 'Locale' in window.Intl) { // Full locale ('es-ES'), add fallback to the base ('es') + const localeObj = new Intl.Locale(locale); + const withoutScript = `${localeObj.language}-${localeObj.region}`; + const base = localeObj.language; + + if (supportedLocales[withoutScript]) toUse.push(withoutScript); + if (supportedLocales[base]) toUse.push(base); + } else if (locale.includes('-')) { + // legacy logic: if Intl.Locale is not available let langPart = locale.split('-')[0]; if (supportedLocales[langPart]) toUse.push(langPart); } } // remove duplicates return utilArrayUniq(toUse); - } + }; function updateForCurrentLocale() { if (!_localeCode) return; diff --git a/test/spec/core/localizer.js b/test/spec/core/localizer.js index de157710a..690c69444 100644 --- a/test/spec/core/localizer.js +++ b/test/spec/core/localizer.js @@ -90,4 +90,29 @@ describe('iD.coreLocalizer', function() { expect(countDecimalPlaces('10')).to.eql(0); }); }); + + describe('localesToUseFrom', () => { + const SUPPORTED_LANGS = { + en: true, + 'en-AU': true, + fr: true, + zh: true, + 'zh-CN': true, + }; + it.each([ + /* [requested, matching] */ + [[], ['en']], + [['en'], ['en']], + [['en-AU'], ['en-AU', 'en']], + [['zh'], ['zh', 'en']], + [['zh-CN'], ['zh-CN', 'zh', 'en']], + [['zh-Hans-CN'], ['zh-CN', 'zh', 'en']], + [['zh-Hans'], ['zh', 'en']], + [['fr-Latn'], ['fr', 'en']], + ])('resolves %s to %s', (requested, matching) => { + const localiser = iD.coreLocalizer(); + localiser.preferredLocaleCodes(requested); + expect(localiser.localesToUseFrom(SUPPORTED_LANGS)).toStrictEqual(matching); + }); + }); });