Add method to get client locale

This commit is contained in:
Christian Kaisermann 2019-06-18 18:02:02 -03:00
parent 563b400193
commit 8de3d27e09
6 changed files with 175 additions and 51 deletions

View File

@ -15,7 +15,7 @@
The `locale` store defines what is the current locale.
```js
import { locale, dictionary } from 'svelte-i18n'
import { locale, dictionary, getClientLocale } from 'svelte-i18n'
// Set the current locale to en-US
locale.set('en-US')
@ -24,8 +24,26 @@ locale.set('en-US')
locale.subscribe(() => {
console.log('locale change')
})
// svelte-i18n exports a method to help getting the current client locale
locale.set(
getClientLocale({
// the fallback locale, if didn't find any
fallback: 'en-US',
// set to 'true' to check the 'window.navigator.language'
navigator: true,
// set the key name to look for a locale on 'window.location.search'
// 'example.com?locale=en-US'
search: 'lang',
// set the key name to look for a locale on 'window.location.hash'
// 'example.com#locale=en-US'
hash: 'locale',
}),
)
```
If a locale with the format `xx-YY` is not found, `svelte-i18n` looks for the locale `xx` as well.
---
### The dictionary

View File

@ -1,12 +1,4 @@
import { locale, dictionary } from 'svelte-i18n'
// setting the locale
locale.set('pt')
// subscribe to locale changes
locale.subscribe(() => {
console.log('locale change')
})
import { locale, dictionary, getClientLocale } from 'svelte-i18n'
// defining a locale dictionary
dictionary.set({
@ -16,7 +8,8 @@ dictionary.set({
ask: 'Por favor, digite seu nome',
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}}',
},
en: {
@ -25,7 +18,20 @@ dictionary.set({
ask: 'Please type your name',
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}}'
photos:
'You have {n, plural, =0 {no photos.} =1 {one photo.} other {# photos.}}',
cats: 'I have {n, number} {n,plural,one{cat}other{cats}}',
},
})
})
locale.set(
getClientLocale({
navigator: true,
hash: 'lang',
default: 'pt',
}),
)
locale.subscribe(l => {
console.log('locale change', l)
})

2
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "svelte-i18n",
"version": "1.0.6-beta",
"version": "1.0.7",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@ -2,11 +2,26 @@ import { writable, derived } from 'svelte/store'
import resolvePath from 'object-resolve-path'
import IntlMessageFormat from 'intl-messageformat'
import memoize from 'micro-memoize'
import { capital, title, upper, lower } from './utils.js'
import { capital, title, upper, lower, getClientLocale } from './utils.js'
let currentLocale
let currentDictionary
const getAvailableLocale = newLocale => {
if (currentDictionary[newLocale]) return newLocale
// istanbul ignore else
if (typeof newLocale === 'string') {
const fallbackLocale = newLocale.split('-').shift()
if (currentDictionary[fallbackLocale]) {
return fallbackLocale
}
}
return null
}
const getMessageFormatter = memoize(
(message, locale, formats) => new IntlMessageFormat(message, locale, formats),
)
@ -37,22 +52,16 @@ const getLocalizedMessage = (path, interpolations, locale = currentLocale) => {
getLocalizedMessage.time = (t, format = 'short', locale) =>
formatMessage(`{t,time,${format}}`, { t }, locale)
getLocalizedMessage.date = (d, format = 'short', locale) =>
formatMessage(`{d,date,${format}}`, { d }, locale)
getLocalizedMessage.number = (n, locale) =>
formatMessage('{n,number}', { n }, locale)
getLocalizedMessage.capital = (path, interpolations, locale) =>
capital(getLocalizedMessage(path, interpolations, locale))
getLocalizedMessage.title = (path, interpolations, locale) =>
title(getLocalizedMessage(path, interpolations, locale))
getLocalizedMessage.upper = (path, interpolations, locale) =>
upper(getLocalizedMessage(path, interpolations, locale))
getLocalizedMessage.lower = (path, interpolations, locale) =>
lower(getLocalizedMessage(path, interpolations, locale))
@ -62,10 +71,21 @@ dictionary.subscribe(newDictionary => {
})
const locale = writable({})
const localeSet = locale.set
locale.set = newLocale => {
const availableLocale = getAvailableLocale(newLocale)
if (availableLocale) {
return localeSet(availableLocale)
}
console.warn(`[svelte-i18n] Locale "${newLocale}" not found.`)
return localeSet(newLocale)
}
locale.update = fn => localeSet(fn(currentLocale))
locale.subscribe(newLocale => {
currentLocale = newLocale
})
const format = derived(locale, () => getLocalizedMessage)
const format = derived([locale, dictionary], () => getLocalizedMessage)
export { locale, format as _, format, dictionary }
export { locale, format as _, format, dictionary, getClientLocale }

View File

@ -2,3 +2,36 @@ export const capital = str => str.replace(/(^|\s)\S/, l => l.toUpperCase())
export const title = str => str.replace(/(^|\s)\S/g, l => l.toUpperCase())
export const upper = str => str.toLocaleUpperCase()
export const lower = str => str.toLocaleLowerCase()
export const getClientLocale = ({ navigator, hash, search, fallback } = {}) => {
let locale
const getFromURL = (urlPart, key) => {
const keyVal = urlPart
.substr(1)
.split('&')
.find(i => i.indexOf(key) === 0)
if (keyVal) {
return keyVal.split('=').pop()
}
}
// istanbul ignore else
if (typeof window !== 'undefined') {
if (navigator) {
// istanbul ignore next
locale = window.navigator.language || window.navigator.languages[0]
}
if (search) {
locale = getFromURL(window.location.search, search)
}
if (hash) {
locale = getFromURL(window.location.hash, hash)
}
}
return locale || fallback
}

View File

@ -1,4 +1,4 @@
import { dictionary, locale, format } from '../src/index'
import { dictionary, locale, format, getClientLocale } from '../src/index'
let _
let currentLocale
@ -42,7 +42,19 @@ it('should change locale', () => {
expect(currentLocale).toBe('en')
})
it('should fallback to message id if id is not found', () => {
it('should fallback to existing locale', () => {
locale.set('pt-BR')
expect(currentLocale).toBe('pt')
locale.set('en-US')
expect(currentLocale).toBe('en')
locale.set('non-existent')
expect(currentLocale).toBe('non-existent')
})
it('should fallback to message id if id is not found', () => {
locale.set('en')
expect(_('batatinha')).toBe('batatinha')
})
@ -71,39 +83,74 @@ it('should interpolate message with variables according to passed locale', () =>
})
describe('utilities', () => {
beforeAll(() => {
locale.set('en')
describe('get locale', () => {
beforeEach(() => {
delete window.location
window.location = {
hash: '',
search: '',
}
})
it('should get the locale based on the passed hash parameter', () => {
window.location.hash = '#locale=en-US&lang=pt-BR'
expect(getClientLocale({ hash: 'locale' })).toBe('en-US')
expect(getClientLocale({ hash: 'lang' })).toBe('pt-BR')
})
it('should get the locale based on the passed search parameter', () => {
window.location.search = '?locale=en-US&lang=pt-BR'
expect(getClientLocale({ search: 'locale' })).toBe('en-US')
expect(getClientLocale({ search: 'lang' })).toBe('pt-BR')
})
it('should get the locale based on the navigator language', () => {
expect(getClientLocale({ navigator: true })).toBe(
window.navigator.language,
)
})
it('should get the fallback locale', () => {
expect(getClientLocale({ navigator: false, fallback: 'pt' })).toBe('pt')
expect(getClientLocale({ hash: 'locale', fallback: 'pt' })).toBe('pt')
})
})
it('should capital a translated message', () => {
expect(_.capital('hi')).toBe('Hi yo')
})
describe('format utils', () => {
beforeAll(() => {
locale.set('en')
})
it('should title a translated message', () => {
expect(_.title('hi')).toBe('Hi Yo')
})
it('should capital a translated message', () => {
expect(_.capital('hi')).toBe('Hi yo')
})
it('should lowercase a translated message', () => {
expect(_.lower('hi')).toBe('hi yo')
})
it('should title a translated message', () => {
expect(_.title('hi')).toBe('Hi Yo')
})
it('should uppercase a translated message', () => {
expect(_.upper('hi')).toBe('HI YO')
})
it('should lowercase a translated message', () => {
expect(_.lower('hi')).toBe('hi yo')
})
const date = new Date(2019, 3, 24, 23, 45)
it('should format a time value', () => {
locale.set('en')
expect(_.time(date)).toBe('11:45 PM')
expect(_.time(date, 'medium')).toBe('11:45:00 PM')
})
it('should uppercase a translated message', () => {
expect(_.upper('hi')).toBe('HI YO')
})
it('should format a date value', () => {
expect(_.date(date)).toBe('4/24/19')
expect(_.date(date, 'medium')).toBe('Apr 24, 2019')
})
// number
const date = new Date(2019, 3, 24, 23, 45)
it('should format a time value', () => {
locale.set('en')
expect(_.time(date)).toBe('11:45 PM')
expect(_.time(date, 'medium')).toBe('11:45:00 PM')
})
it('should format a date value', () => {
expect(_.date(date)).toBe('4/24/19')
expect(_.date(date, 'medium')).toBe('Apr 24, 2019')
})
// number
it('should format a date value', () => {
expect(_.number(123123123)).toBe('123,123,123')
})
})
})