From 44b7f797e49a53d5bf6137bc5982cf6eb46b83b3 Mon Sep 17 00:00:00 2001 From: Oliver Shi Date: Wed, 18 Aug 2021 17:44:19 -0400 Subject: [PATCH] =?UTF-8?q?update=20canonicalizeLocale=20to=20handle=20?= =?UTF-8?q?=E4=B8=AD=E6=96=87(chinese)=20(#239)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit J=SLAP-1526 TEST=auto,manual unit tests ran build with zh-hans-ch in locale config, saw that the generated source code used the correct locale in the page tried building a page with a locale with >3 sections, got a jambo warning but build continued without changing the locale --- src/utils/i18nutils.js | 84 ++++++++++++++++++++---- tests/acceptance/suites/themeupgrader.js | 24 +++---- tests/utils/i18nutils.js | 62 ++++++++++++++++- 3 files changed, 145 insertions(+), 25 deletions(-) diff --git a/src/utils/i18nutils.js b/src/utils/i18nutils.js index 54d1a34e..2c681410 100644 --- a/src/utils/i18nutils.js +++ b/src/utils/i18nutils.js @@ -1,25 +1,87 @@ +const UserError = require('../errors/usererror'); + /** * Normalizes a locale code * * @param {string} localeCode * @returns {string} */ -canonicalizeLocale = function(localeCode) { +exports.canonicalizeLocale = function(localeCode) { if (!localeCode) { return; } - const localeCodeSections = localeCode.replace('-', '_') - .split('_'); - - const languageIndex = 0; - const regionIndex = 1; + const { language, modifier, region } = parseLocale(localeCode); + return formatLocale(language, modifier, region); +} - localeCodeSections[languageIndex] = localeCodeSections[languageIndex].toLowerCase(); +/** + * Parses a locale code into its constituent parts. + * Performs case formatting on the result. + * + * @param {string} localeCode + * @returns { language: string, modifier?: string, region?: string } + */ +function parseLocale(localeCode) { + const localeCodeSections = localeCode.replace(/-/g, '_').split('_'); + const language = localeCodeSections[0].toLowerCase(); + const parseModifierAndRegion = () => { + const numSections = localeCodeSections.length; + if (numSections === 1) { + return {}; + } else if (numSections === 2 && language === 'zh') { + const ambiguous = localeCodeSections[1].toLowerCase(); + if (['hans', 'hant'].includes(ambiguous)) { + return { modifier: ambiguous }; + } else { + return { region: ambiguous }; + } + } else if (numSections === 2) { + return { region: localeCodeSections[1] }; + } else if (numSections === 3) { + return { + modifier: localeCodeSections[1], + region: localeCodeSections[2] + }; + } else if (numSections > 3) { + throw new UserError( + `Encountered strangely formatted locale "${localeCode}", ` + + `with ${numSections} sections.`); + } + } + const capitalizeFirstLetterOnly = raw => { + return raw.charAt(0).toUpperCase() + raw.slice(1).toLowerCase(); + } + const parsedLocale = { + language, + ...parseModifierAndRegion() + }; - if (localeCodeSections.length > regionIndex) { - localeCodeSections[regionIndex] = localeCodeSections[regionIndex].toUpperCase(); + if (parsedLocale.modifier) { + parsedLocale.modifier = capitalizeFirstLetterOnly(parsedLocale.modifier); + } + if (parsedLocale.region) { + parsedLocale.region = parsedLocale.region.toUpperCase(); } - return localeCodeSections.join('_'); + return parsedLocale; } -exports.canonicalizeLocale = canonicalizeLocale; \ No newline at end of file +exports.parseLocale = parseLocale; + +/** + * Formats a locale code given its constituent parts. + * + * @param {string} language zh in zh-Hans_CH + * @param {string?} modifier Hans in zh-Hans_CH + * @param {string?} region CH in zh-Hans_CH + * @returns + */ +function formatLocale(language, modifier, region) { + let result = language.toLowerCase(); + if (modifier) { + result += '-' + modifier; + } + if (region) { + result += '_' + region; + } + return result; +} \ No newline at end of file diff --git a/tests/acceptance/suites/themeupgrader.js b/tests/acceptance/suites/themeupgrader.js index 21faef2a..7cc22707 100644 --- a/tests/acceptance/suites/themeupgrader.js +++ b/tests/acceptance/suites/themeupgrader.js @@ -5,7 +5,7 @@ const ThemeManager = require('../../../src/utils/thememanager'); const path = require('path'); ThemeManager.getRepoForTheme = () => { - return path.resolve(__dirname, '../test-themes/basic-flow'); + return path.resolve(__dirname, '../test-themes/basic-flow'); }; //Silence error logs @@ -14,15 +14,15 @@ console.error = jest.fn(); afterAll(() => console.error = error); it('tests upgrade fail', () => runInPlayground(async t => { - await git.cwd(process.cwd()); - await t.jambo('init'); - await t.jambo( - 'import --themeUrl ../test-themes/basic-flow'); - await git.add('.'); - await git.commit('theme'); - await expect(t.jambo('upgrade --branch fail')).rejects.toThrow( - /Remote branch fail not found in upstream origin/); - const diff = await git.diff(); - expect(diff).toBe(''); - })); + await git.cwd(process.cwd()); + await t.jambo('init'); + await t.jambo( + 'import --themeUrl ../test-themes/basic-flow'); + await git.add('.'); + await git.commit('theme'); + await expect(t.jambo('upgrade --branch fail')).rejects.toThrow( + /Remote branch fail not found in upstream origin/); + const diff = await git.diff(); + expect(diff).toBe(''); +})); diff --git a/tests/utils/i18nutils.js b/tests/utils/i18nutils.js index daba290e..c466f1aa 100644 --- a/tests/utils/i18nutils.js +++ b/tests/utils/i18nutils.js @@ -1,4 +1,4 @@ -const { canonicalizeLocale } = require('../../src/utils/i18nutils'); +const { canonicalizeLocale, parseLocale } = require('../../src/utils/i18nutils'); describe('canonicalizeLocale correctly normalizes locales', () => { it('converts language to lower case and region to upper case', () => { @@ -12,4 +12,62 @@ describe('canonicalizeLocale correctly normalizes locales', () => { const canonicalizedLocale = canonicalizeLocale(locale); expect(canonicalizedLocale).toEqual('fr_CH'); }); -}); \ No newline at end of file + + describe('works for chinese', () => { + function runTest(testName, inputLocale, expectedLocale) { + it(testName, () => { + expect(canonicalizeLocale(inputLocale)).toEqual(expectedLocale); + }); + } + const testCases = [ + ['using dashes', 'zh-Hans-CH'], + ['using underscores', 'zh_Hans_CH'], + ['underscore then dash', 'zh_Hans-CH'], + ['dash then underscore', 'zh-Hans_CH'], + ['updates casing', 'ZH-hans_Ch'], + ['does not have region code', 'zh-hans', 'zh-Hans'], + ['has region but no modifier', 'zh-cH', 'zh_CH'] + ]; + const expected = 'zh-Hans_CH'; + for (const [testName, inputLocale, specificExpected] of testCases) { + runTest(testName, inputLocale, specificExpected || expected); + } + }); +}); + +describe('parseLocale', () => { + it('performs case formatting', () => { + expect(parseLocale('Zh-hans-Ch')).toEqual({ + language: 'zh', + modifier: 'Hans', + region: 'CH' + }) + }); + + it('chinese with modifier only', () => { + expect(parseLocale('ZH_HANS')).toEqual({ + language: 'zh', + modifier: 'Hans' + }) + }); + + it('chinese with region only', () => { + expect(parseLocale('ZH-cH')).toEqual({ + language: 'zh', + region: 'CH' + }) + }); + + it('2 section non-chinese locale', () => { + expect(parseLocale('FR-freE')).toEqual({ + language: 'fr', + region: 'FREE' + }); + }); + + it('simple language', () => { + expect(parseLocale('FR')).toEqual({ + language: 'fr' + }); + }); +});