From e5d7b84241bd7e3fdd833e82dd8a9a8f251f023c Mon Sep 17 00:00:00 2001 From: Christian Kaisermann Date: Tue, 19 Nov 2019 21:04:50 -0300 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20also=20look=20for=20mess?= =?UTF-8?q?age=20in=20generic=20locale?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issues: #19 --- src/client/index.ts | 29 +++++++++++++++++--------- src/client/utils.ts | 30 +++++---------------------- test/client/index.test.ts | 43 +++++++++++++++++++-------------------- test/fixtures/en-GB.json | 3 +++ test/fixtures/en.json | 3 ++- test/fixtures/es.json | 3 ++- test/fixtures/pt.json | 3 ++- yarn.lock | 16 +-------------- 8 files changed, 55 insertions(+), 75 deletions(-) create mode 100644 test/fixtures/en-GB.json diff --git a/src/client/index.ts b/src/client/index.ts index 7882ca9..453cbff 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -2,7 +2,7 @@ import { writable, derived } from 'svelte/store' import resolvePath from 'object-resolve-path' import memoize from 'micro-memoize' -import { capital, title, upper, lower, getClientLocale } from './utils' +import { capital, title, upper, lower, getClientLocale, getGenericLocaleFrom } from './utils' import { MessageObject, Formatter } from './types' import { getMessageFormatter, @@ -14,7 +14,9 @@ import { let currentLocale: string let currentDictionary: Record -function getAvailableLocale(locale: string) { +const hasLocale = (locale: string) => locale in currentDictionary + +function getAvailableLocale(locale: string): { locale: string; loader?: () => Promise } { if (currentDictionary[locale]) { if (typeof currentDictionary[locale] === 'function') { return { locale, loader: currentDictionary[locale] } @@ -22,22 +24,29 @@ function getAvailableLocale(locale: string) { return { locale } } - locale = locale.split('-').shift() // - if (currentDictionary[locale]) { - if (typeof currentDictionary[locale] === 'function') { - return { locale, loader: currentDictionary[locale] } - } - return { locale } + locale = getGenericLocaleFrom(locale) + if (locale != null) { + return getAvailableLocale(locale) } return { locale: null } } -const lookupMessage = memoize((path: string, locale: string) => { +const lookupMessage = memoize((path: string, locale: string): string => { if (path in currentDictionary[locale]) { return currentDictionary[locale][path] } - return resolvePath(currentDictionary[locale], path) + + const message = resolvePath(currentDictionary[locale], path) + if (message == null) { + const genericLocale = getGenericLocaleFrom(locale) + if (genericLocale != null && hasLocale(genericLocale)) { + return lookupMessage(path, genericLocale) + } + return null + } + + return message }) const formatMessage: Formatter = (id, options = {}) => { diff --git a/src/client/utils.ts b/src/client/utils.ts index 5f6a682..9192b44 100644 --- a/src/client/utils.ts +++ b/src/client/utils.ts @@ -5,12 +5,15 @@ export const title = (str: string) => export const upper = (str: string) => str.toLocaleUpperCase() export const lower = (str: string) => str.toLocaleLowerCase() +export function getGenericLocaleFrom(locale: string) { + return locale.length > 2 ? locale.split('-').shift() : null +} + export const getClientLocale = ({ navigator, hash, search, default: defaultLocale, - fallback = defaultLocale, }: { navigator?: boolean hash?: string @@ -47,28 +50,5 @@ export const getClientLocale = ({ } } - return locale || defaultLocale || fallback + return locale || defaultLocale } - -// function mergeDeep(target: any, source: any) { -// const isObject = (obj: any) => obj && typeof obj === 'object' - -// if (!isObject(target) || !isObject(source)) { -// return source -// } - -// Object.keys(source).forEach(key => { -// const targetValue = target[key] -// const sourceValue = source[key] - -// if (Array.isArray(targetValue) && Array.isArray(sourceValue)) { -// target[key] = targetValue.concat(sourceValue) -// } else if (isObject(targetValue) && isObject(sourceValue)) { -// target[key] = mergeDeep(Object.assign({}, targetValue), sourceValue) -// } else { -// target[key] = sourceValue -// } -// }) - -// return target -// } diff --git a/test/client/index.test.ts b/test/client/index.test.ts index 24de811..d20a4dd 100644 --- a/test/client/index.test.ts +++ b/test/client/index.test.ts @@ -16,6 +16,7 @@ let currentLocale: string const dict = { pt: require('../fixtures/pt.json'), en: require('../fixtures/en.json'), + 'en-GB': require('../fixtures/en-GB.json'), } format.subscribe(formatFn => { @@ -61,6 +62,13 @@ describe('dictionary', () => { }) describe('formatting', () => { + it('should translate to current locale', () => { + locale.set('pt') + expect(_('switch.lang')).toBe('Trocar idioma') + locale.set('en') + expect(_('switch.lang')).toBe('Switch language') + }) + it('should fallback to message id if id is not found', () => { locale.set('en') expect(_('batatinha.quente')).toBe('batatinha.quente') @@ -71,10 +79,9 @@ describe('formatting', () => { expect(_('batatinha.quente', { default: 'Hot Potato' })).toBe('Hot Potato') }) - it('should translate to current locale', () => { - locale.set('pt') - expect(_('switch.lang')).toBe('Trocar idioma') - locale.set('en') + it('should fallback to generic locale XX if id not found in XX-YY', () => { + locale.set('en-GB') + expect(_('sneakers')).toBe('trainers') expect(_('switch.lang')).toBe('Switch language') }) @@ -91,15 +98,15 @@ describe('formatting', () => { }) it('should interpolate message with variables', () => { - expect(_('greeting.message', { values: { name: 'Chris' } })).toBe( - 'Hello Chris, how are you?', - ) + locale.set('en') + expect(_('greeting.message', { values: { name: 'Chris' } })).toBe('Hello Chris, how are you?') }) it('should interpolate message with variables according to passed locale', () => { - expect( - _('greeting.message', { values: { name: 'Chris' }, locale: 'pt' }), - ).toBe('Olá Chris, como vai?') + locale.set('en') + expect(_('greeting.message', { values: { name: 'Chris' }, locale: 'pt' })).toBe( + 'Olá Chris, como vai?', + ) }) }) @@ -126,9 +133,7 @@ describe('utilities', () => { }) it('should get the locale based on the navigator language', () => { - expect(getClientLocale({ navigator: true })).toBe( - window.navigator.language, - ) + expect(getClientLocale({ navigator: true })).toBe(window.navigator.language) }) it('should get the fallback locale', () => { @@ -163,9 +168,7 @@ describe('utilities', () => { locale.set('en') expect(_.time(date)).toBe('11:45 PM') expect(_.time(date, { format: 'medium' })).toBe('11:45:00 PM') - expect(_.time(date, { format: 'medium', locale: 'pt-BR' })).toBe( - '23:45:00', - ) + expect(_.time(date, { format: 'medium', locale: 'pt-BR' })).toBe('23:45:00') }) it('should format a date value', () => { @@ -224,12 +227,8 @@ describe('custom formats', () => { expect(_.number(123123123, { format: 'usd' })).toContain('$123,123,123.00') expect(_.number(123123123, { format: 'brl' })).toContain('R$123,123,123.00') - expect(_.date(new Date(2019, 0, 1), { format: 'customDate' })).toEqual( - '2019 AD', - ) + expect(_.date(new Date(2019, 0, 1), { format: 'customDate' })).toEqual('2019 AD') - expect( - _.time(new Date(2019, 0, 1, 2, 0, 0), { format: 'customTime' }), - ).toEqual('Jan, 02') + expect(_.time(new Date(2019, 0, 1, 2, 0, 0), { format: 'customTime' })).toEqual('Jan, 02') }) }) diff --git a/test/fixtures/en-GB.json b/test/fixtures/en-GB.json new file mode 100644 index 0000000..943dc25 --- /dev/null +++ b/test/fixtures/en-GB.json @@ -0,0 +1,3 @@ +{ + "sneakers": "trainers" +} diff --git a/test/fixtures/en.json b/test/fixtures/en.json index 321ddb2..e5a3898 100644 --- a/test/fixtures/en.json +++ b/test/fixtures/en.json @@ -6,5 +6,6 @@ "message": "Hello {name}, how are you?" }, "photos": "You have {n, plural, =0 {no photos.} =1 {one photo.} other {# photos.}}", - "cats": "I have {n, number} {n,plural,one{cat}other{cats}}" + "cats": "I have {n, number} {n,plural,one{cat}other{cats}}", + "sneakers": "sneakers" } diff --git a/test/fixtures/es.json b/test/fixtures/es.json index 1c2e599..9396956 100644 --- a/test/fixtures/es.json +++ b/test/fixtures/es.json @@ -6,5 +6,6 @@ "message": "Hola {name}, cómo estás?" }, "photos": "Tienes {n, plural, =0 {0 fotos.} =1 {una foto.} other {# fotos.}}", - "cats": "Yo tengo {n, number} {n,plural,one{gato}other{gatos}}" + "cats": "Yo tengo {n, number} {n,plural,one{gato}other{gatos}}", + "sneakers": "zapatillas" } diff --git a/test/fixtures/pt.json b/test/fixtures/pt.json index af4d717..cfa7561 100644 --- a/test/fixtures/pt.json +++ b/test/fixtures/pt.json @@ -6,5 +6,6 @@ "message": "Olá {name}, como vai?" }, "photos": "Você {n, plural, =0 {não tem fotos.} =1 {tem uma foto.} other {tem # fotos.}}", - "cats": "Tenho {n, number} {n,plural,=0{gatos}one{gato}other{gatos}}" + "cats": "Tenho {n, number} {n,plural,=0{gatos}one{gato}other{gatos}}", + "sneakers": "tênis" } diff --git a/yarn.lock b/yarn.lock index ea06f65..f896c2f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4322,11 +4322,6 @@ require-main-filename@^2.0.0: resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== -require-relative@^0.8.7: - version "0.8.7" - resolved "https://registry.yarnpkg.com/require-relative/-/require-relative-0.8.7.tgz#7999539fc9e047a37928fa196f8e1563dabd36de" - integrity sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4= - resolve-cwd@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" @@ -4409,15 +4404,6 @@ rollup-plugin-commonjs@^10.1.0: resolve "^1.11.0" rollup-pluginutils "^2.8.1" -rollup-plugin-svelte@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/rollup-plugin-svelte/-/rollup-plugin-svelte-5.1.1.tgz#0094f94e7e6ff7579bcd9f7769b454751ba670e1" - integrity sha512-wP3CnKHjR4fZUgNm5Iey7eItnxwnH/nAw568WJ8dpMSchBxxZ/DmKSx8e6h8k/B6SwG1wfGvWehadFJHcuFFSw== - dependencies: - require-relative "^0.8.7" - rollup-pluginutils "^2.3.3" - sourcemap-codec "^1.4.4" - rollup-plugin-terser@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-5.1.2.tgz#3e41256205cb75f196fc70d4634227d1002c255c" @@ -4447,7 +4433,7 @@ rollup-pluginutils@2.8.1: dependencies: estree-walker "^0.6.1" -rollup-pluginutils@^2.3.3, rollup-pluginutils@^2.8.1: +rollup-pluginutils@^2.8.1: version "2.8.2" resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz#72f2af0748b592364dbd3389e600e5a9444a351e" integrity sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==