diff --git a/static/js/units-i18n.js b/static/js/units-i18n.js index 835bbb908..2ccbee36a 100644 --- a/static/js/units-i18n.js +++ b/static/js/units-i18n.js @@ -1,3 +1,5 @@ +import { parseLocale } from './utils'; + /** * Object containing imperial units */ @@ -24,23 +26,12 @@ export const LOCALE_UNIT_MAP = { default: IMPERIAL }, es: { - US: IMPERIAL, - default: METRIC - }, - fr: { - default: METRIC - }, - de: { - default: METRIC - }, - it: { - default: METRIC - }, - ja: { - default: METRIC + US: IMPERIAL } }; +const unitSystemFallback = METRIC; + /** * Gets the distance unit for the specified locale * @param {string} locale @@ -57,12 +48,7 @@ export function getDistanceUnit(locale){ * @returns {Object} */ function getUnitsForLocale(locale) { - const language = locale.substring(0,2); - // Note: Getting region this way will be invalid if script tags are used in the future. - // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl - // Optionally, we can use Intl.locale() but we would need to polyfill it for IE11 - const region = locale.substring(3,5); - const unitSystemFallback = METRIC; + const { language, region } = parseLocale(locale); const isKnownLanguage = (language in LOCALE_UNIT_MAP); if (!isKnownLanguage) { @@ -71,7 +57,7 @@ function getUnitsForLocale(locale) { const isKnownRegion = (region in LOCALE_UNIT_MAP[language]); if (!isKnownRegion) { - return Object.assign({}, LOCALE_UNIT_MAP[language]['default']); + return Object.assign({}, LOCALE_UNIT_MAP[language]['default'] || unitSystemFallback); } return Object.assign({}, LOCALE_UNIT_MAP[language][region]); diff --git a/static/js/utils.js b/static/js/utils.js index eb42a9af4..8488b6b79 100644 --- a/static/js/utils.js +++ b/static/js/utils.js @@ -13,4 +13,57 @@ export function canonicalizeBoolean (value) { } else { return false; } -} \ No newline at end of file +} + +/** + * Parses a locale code into its constituent parts. + * Performs case formatting on the result. + * + * @param {string} localeCode + * @returns { language: string, modifier?: string, region?: string } + */ +export 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) { + console.error( + `Encountered strangely formatted locale "${localeCode}", ` + + `with ${numSections} sections.`); + return { language: 'en' }; + } + } + const capitalizeFirstLetterOnly = raw => { + return raw.charAt(0).toUpperCase() + raw.slice(1).toLowerCase(); + } + const parsedLocale = { + language, + ...parseModifierAndRegion() + }; + + if (parsedLocale.modifier) { + parsedLocale.modifier = capitalizeFirstLetterOnly(parsedLocale.modifier); + } + if (parsedLocale.region) { + parsedLocale.region = parsedLocale.region.toUpperCase(); + } + + return parsedLocale; +} diff --git a/tests/static/js/units-18n.js b/tests/static/js/units-18n.js index b61e44c3c..76b461ceb 100644 --- a/tests/static/js/units-18n.js +++ b/tests/static/js/units-18n.js @@ -16,11 +16,3 @@ describe('getDistanceUnit', () => { expect(unit).toEqual('km'); }); }); - -describe('unit map', () => { - it('All languages must have a default unit', () => { - Object.entries(LOCALE_UNIT_MAP).forEach(([languageMap, regionMap]) => { - expect('default' in regionMap).toBeTruthy(); - }); - }); -}); \ No newline at end of file diff --git a/tests/static/js/utils.js b/tests/static/js/utils.js index 6a4e1f01b..703e21cf0 100644 --- a/tests/static/js/utils.js +++ b/tests/static/js/utils.js @@ -1,4 +1,4 @@ -import { canonicalizeBoolean } from '../../../static/js/utils'; +import { canonicalizeBoolean, parseLocale } from '../../../static/js/utils'; describe('canonicalizeBoolean works properly', () => { it('case-insensitive string representations of "true" return true', () => { @@ -25,4 +25,41 @@ describe('canonicalizeBoolean works properly', () => { const result = canonicalizeBoolean({}); expect(result).toEqual(false); }); -}) \ No newline at end of file +}) + +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' + }); + }); +});