feat: 🎸 also look for message in generic locale

Issues: #19
This commit is contained in:
Christian Kaisermann 2019-11-19 21:04:50 -03:00
parent 31d96f24ff
commit e5d7b84241
8 changed files with 55 additions and 75 deletions

View File

@ -2,7 +2,7 @@ import { writable, derived } from 'svelte/store'
import resolvePath from 'object-resolve-path' import resolvePath from 'object-resolve-path'
import memoize from 'micro-memoize' 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 { MessageObject, Formatter } from './types'
import { import {
getMessageFormatter, getMessageFormatter,
@ -14,7 +14,9 @@ import {
let currentLocale: string let currentLocale: string
let currentDictionary: Record<string, any> let currentDictionary: Record<string, any>
function getAvailableLocale(locale: string) { const hasLocale = (locale: string) => locale in currentDictionary
function getAvailableLocale(locale: string): { locale: string; loader?: () => Promise<any> } {
if (currentDictionary[locale]) { if (currentDictionary[locale]) {
if (typeof currentDictionary[locale] === 'function') { if (typeof currentDictionary[locale] === 'function') {
return { locale, loader: currentDictionary[locale] } return { locale, loader: currentDictionary[locale] }
@ -22,22 +24,29 @@ function getAvailableLocale(locale: string) {
return { locale } return { locale }
} }
locale = locale.split('-').shift() // locale = getGenericLocaleFrom(locale)
if (currentDictionary[locale]) { if (locale != null) {
if (typeof currentDictionary[locale] === 'function') { return getAvailableLocale(locale)
return { locale, loader: currentDictionary[locale] }
}
return { locale }
} }
return { locale: null } return { locale: null }
} }
const lookupMessage = memoize((path: string, locale: string) => { const lookupMessage = memoize((path: string, locale: string): string => {
if (path in currentDictionary[locale]) { if (path in currentDictionary[locale]) {
return currentDictionary[locale][path] 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 = {}) => { const formatMessage: Formatter = (id, options = {}) => {

View File

@ -5,12 +5,15 @@ export const title = (str: string) =>
export const upper = (str: string) => str.toLocaleUpperCase() export const upper = (str: string) => str.toLocaleUpperCase()
export const lower = (str: string) => str.toLocaleLowerCase() export const lower = (str: string) => str.toLocaleLowerCase()
export function getGenericLocaleFrom(locale: string) {
return locale.length > 2 ? locale.split('-').shift() : null
}
export const getClientLocale = ({ export const getClientLocale = ({
navigator, navigator,
hash, hash,
search, search,
default: defaultLocale, default: defaultLocale,
fallback = defaultLocale,
}: { }: {
navigator?: boolean navigator?: boolean
hash?: string 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
// }

View File

@ -16,6 +16,7 @@ let currentLocale: string
const dict = { const dict = {
pt: require('../fixtures/pt.json'), pt: require('../fixtures/pt.json'),
en: require('../fixtures/en.json'), en: require('../fixtures/en.json'),
'en-GB': require('../fixtures/en-GB.json'),
} }
format.subscribe(formatFn => { format.subscribe(formatFn => {
@ -61,6 +62,13 @@ describe('dictionary', () => {
}) })
describe('formatting', () => { 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', () => { it('should fallback to message id if id is not found', () => {
locale.set('en') locale.set('en')
expect(_('batatinha.quente')).toBe('batatinha.quente') expect(_('batatinha.quente')).toBe('batatinha.quente')
@ -71,10 +79,9 @@ describe('formatting', () => {
expect(_('batatinha.quente', { default: 'Hot Potato' })).toBe('Hot Potato') expect(_('batatinha.quente', { default: 'Hot Potato' })).toBe('Hot Potato')
}) })
it('should translate to current locale', () => { it('should fallback to generic locale XX if id not found in XX-YY', () => {
locale.set('pt') locale.set('en-GB')
expect(_('switch.lang')).toBe('Trocar idioma') expect(_('sneakers')).toBe('trainers')
locale.set('en')
expect(_('switch.lang')).toBe('Switch language') expect(_('switch.lang')).toBe('Switch language')
}) })
@ -91,15 +98,15 @@ describe('formatting', () => {
}) })
it('should interpolate message with variables', () => { it('should interpolate message with variables', () => {
expect(_('greeting.message', { values: { name: 'Chris' } })).toBe( locale.set('en')
'Hello Chris, how are you?', expect(_('greeting.message', { values: { name: 'Chris' } })).toBe('Hello Chris, how are you?')
)
}) })
it('should interpolate message with variables according to passed locale', () => { it('should interpolate message with variables according to passed locale', () => {
expect( locale.set('en')
_('greeting.message', { values: { name: 'Chris' }, locale: 'pt' }), expect(_('greeting.message', { values: { name: 'Chris' }, locale: 'pt' })).toBe(
).toBe('Olá Chris, como vai?') 'Olá Chris, como vai?',
)
}) })
}) })
@ -126,9 +133,7 @@ describe('utilities', () => {
}) })
it('should get the locale based on the navigator language', () => { it('should get the locale based on the navigator language', () => {
expect(getClientLocale({ navigator: true })).toBe( expect(getClientLocale({ navigator: true })).toBe(window.navigator.language)
window.navigator.language,
)
}) })
it('should get the fallback locale', () => { it('should get the fallback locale', () => {
@ -163,9 +168,7 @@ describe('utilities', () => {
locale.set('en') locale.set('en')
expect(_.time(date)).toBe('11:45 PM') expect(_.time(date)).toBe('11:45 PM')
expect(_.time(date, { format: 'medium' })).toBe('11:45:00 PM') expect(_.time(date, { format: 'medium' })).toBe('11:45:00 PM')
expect(_.time(date, { format: 'medium', locale: 'pt-BR' })).toBe( expect(_.time(date, { format: 'medium', locale: 'pt-BR' })).toBe('23:45:00')
'23:45:00',
)
}) })
it('should format a date value', () => { 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: 'usd' })).toContain('$123,123,123.00')
expect(_.number(123123123, { format: 'brl' })).toContain('R$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( expect(_.date(new Date(2019, 0, 1), { format: 'customDate' })).toEqual('2019 AD')
'2019 AD',
)
expect( expect(_.time(new Date(2019, 0, 1, 2, 0, 0), { format: 'customTime' })).toEqual('Jan, 02')
_.time(new Date(2019, 0, 1, 2, 0, 0), { format: 'customTime' }),
).toEqual('Jan, 02')
}) })
}) })

3
test/fixtures/en-GB.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"sneakers": "trainers"
}

View File

@ -6,5 +6,6 @@
"message": "Hello {name}, how are you?" "message": "Hello {name}, how are you?"
}, },
"photos": "You have {n, plural, =0 {no photos.} =1 {one photo.} other {# photos.}}", "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"
} }

View File

@ -6,5 +6,6 @@
"message": "Hola {name}, cómo estás?" "message": "Hola {name}, cómo estás?"
}, },
"photos": "Tienes {n, plural, =0 {0 fotos.} =1 {una foto.} other {# fotos.}}", "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"
} }

View File

@ -6,5 +6,6 @@
"message": "Olá {name}, como vai?" "message": "Olá {name}, como vai?"
}, },
"photos": "Você {n, plural, =0 {não tem fotos.} =1 {tem uma foto.} other {tem # fotos.}}", "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"
} }

View File

@ -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" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== 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: resolve-cwd@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" 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" resolve "^1.11.0"
rollup-pluginutils "^2.8.1" 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: rollup-plugin-terser@^5.1.2:
version "5.1.2" version "5.1.2"
resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-5.1.2.tgz#3e41256205cb75f196fc70d4634227d1002c255c" 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: dependencies:
estree-walker "^0.6.1" estree-walker "^0.6.1"
rollup-pluginutils@^2.3.3, rollup-pluginutils@^2.8.1: rollup-pluginutils@^2.8.1:
version "2.8.2" version "2.8.2"
resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz#72f2af0748b592364dbd3389e600e5a9444a351e" resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz#72f2af0748b592364dbd3389e600e5a9444a351e"
integrity sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ== integrity sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==