Namespace i18n store methods with "i18n" property

This commit is contained in:
Christian Kaisermann 2018-08-08 19:47:16 -03:00
parent 1e8597150c
commit 07691f3a6d
9 changed files with 1793 additions and 2140 deletions

3
.babelrc Normal file
View File

@ -0,0 +1,3 @@
{
"presets": ["@babel/preset-env"]
}

View File

@ -2,3 +2,4 @@
/.* /.*
/yarn.lock /yarn.lock
/package-lock.json /package-lock.json
/src

View File

@ -12,60 +12,55 @@
import i18n from 'svelte-i18n' import i18n from 'svelte-i18n'
import { Store } from 'svelte/store' import { Store } from 'svelte/store'
const store = new Store() /** i18n(svelteStore, { dictionary }) */
const store = i18n(new Store(), {
/** i18n(svelteStore, arrayOfLocalesObjects) */ dictionary: {
i18n(store, [
{
'pt-BR': { 'pt-BR': {
message: 'Mensagem', message: 'Mensagem',
messages: { messages: {
alert: 'Alerta', alert: 'Alerta',
error: 'Erro' error: 'Erro',
} },
}, },
'en-US': { 'en-US': {
message: 'Message', message: 'Message',
messages: { messages: {
alert: 'Alert', alert: 'Alert',
error: 'Error' error: 'Error',
}
}
}, },
/** Locales are deeply merged */ },
{ },
})
/**
* Extend the initial dictionary.
* Dictionaries are deeply merged.
* */
store.i18n.extendDictionary({
'pt-BR': { 'pt-BR': {
messages: { messages: {
warn: 'Aviso', warn: 'Aviso',
success: 'Sucesso' success: 'Sucesso',
} },
}, },
'en-US': { 'en-US': {
messages: { messages: {
warn: 'Warn', warn: 'Warn',
success: 'Success' success: 'Success',
} },
} },
} })
])
/** Set the initial locale */
store.i18n.setLocale('en-US')
``` ```
### On `templates` ### On `templates`
```html ```html
<div> <div>
{$_('message')}: {upper($_('messages.success'))} {$_('message')}: {$_.upper('messages.success'))}
</div> </div>
<script>
import { upper } from 'svelte-i18n';
export default {
helpers: {
upper,
}
}
</script>
``` ```
Renders: Renders:

3676
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "svelte-i18n", "name": "svelte-i18n",
"version": "0.0.0", "version": "0.0.1",
"license": "MIT", "license": "MIT",
"main": "dist/i18n.js", "main": "dist/i18n.js",
"types": "src/index.d.ts", "types": "src/index.d.ts",
@ -13,9 +13,9 @@
"start": "microbundle watch --name='svelte-i18n'", "start": "microbundle watch --name='svelte-i18n'",
"test": "jest --no-cache --verbose", "test": "jest --no-cache --verbose",
"test:watch": "jest --no-cache --verbose --watchAll", "test:watch": "jest --no-cache --verbose --watchAll",
"lint": "eslint \"*.js\" \"src/**/*.js\"", "lint": "eslint \"src/**/*.js\"",
"format": "prettier --loglevel silent --write \"*.js\" \"src/**/*.js\" && eslint --fix \"*.js\" \"src/**/*.js\"", "format": "prettier --loglevel silent --write \"src/**/*.js\" && eslint --fix \"src/**/*.js\"",
"prepublishOnly": "npm run format && npm run test" "prepublishOnly": "npm run format && npm run test && npm run build"
}, },
"jest": { "jest": {
"verbose": true, "verbose": true,
@ -29,7 +29,8 @@
], ],
"coveragePathIgnorePatterns": [ "coveragePathIgnorePatterns": [
"/node_modules/", "/node_modules/",
"/test/" "/test/",
"/src/formatter.js"
], ],
"coverageThreshold": { "coverageThreshold": {
"global": { "global": {
@ -43,26 +44,26 @@
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.0.0-beta.56", "@babel/core": "^7.0.0-beta.56",
"@babel/preset-env": "^7.0.0-beta.56",
"babel-core": "^7.0.0-bridge.0", "babel-core": "^7.0.0-bridge.0",
"babel-jest": "^23.4.2", "babel-jest": "^23.4.2",
"eslint": "^4.19.1", "eslint": "^5.3.0",
"eslint-config-prettier": "^2.9.0", "eslint-config-prettier": "^2.9.0",
"eslint-config-standard": "^11.0.0", "eslint-config-standard": "^11.0.0",
"eslint-plugin-import": "^2.11.0", "eslint-plugin-import": "^2.13.0",
"eslint-plugin-node": "^6.0.1", "eslint-plugin-node": "^7.0.1",
"eslint-plugin-prettier": "^2.6.0", "eslint-plugin-prettier": "^2.6.2",
"eslint-plugin-promise": "^3.7.0", "eslint-plugin-promise": "^3.8.0",
"eslint-plugin-standard": "^3.1.0", "eslint-plugin-standard": "^3.1.0",
"jest": "^22.4.3", "jest": "^23.4.2",
"microbundle": "^0.4.4", "microbundle": "^0.6.0",
"prettier": "^1.12.1", "prettier": "^1.14.1",
"svelte": "^2.9.10" "svelte": "^2.9.11"
}, },
"peerDependencies": { "peerDependencies": {
"svelte": "^2.9.10" "svelte": "^2.9.11"
}, },
"dependencies": { "dependencies": {
"@babel/preset-env": "^7.0.0-beta.56",
"deepmerge": "^2.1.1", "deepmerge": "^2.1.1",
"object-resolve-path": "^1.1.1" "object-resolve-path": "^1.1.1"
} }

View File

@ -1,8 +1,9 @@
/* istanbul ignore */
/** /**
* Adapted from 'https://github.com/kazupon/vue-i18n/blob/dev/src/format.js' * Adapted from 'https://github.com/kazupon/vue-i18n/blob/dev/src/format.js'
* Copyright (c) 2016 kazuya kawaguchi * Copyright (c) 2016 kazuya kawaguchi
**/ **/
import { isObject, warn } from './utils' import { isObject } from './utils'
const RE_TOKEN_LIST_VALUE = /^(\d)+/ const RE_TOKEN_LIST_VALUE = /^(\d)+/
const RE_TOKEN_NAMED_VALUE = /^(\w)+/ const RE_TOKEN_NAMED_VALUE = /^(\w)+/
@ -79,7 +80,11 @@ export function compile(tokens, values) {
const compiled = [] const compiled = []
let index = 0 let index = 0
const mode = Array.isArray(values) ? 'list' : isObject(values) ? 'named' : 'unknown' const mode = Array.isArray(values)
? 'list'
: isObject(values)
? 'named'
: 'unknown'
if (mode === 'unknown') { if (mode === 'unknown') {
return compiled return compiled
@ -99,14 +104,18 @@ export function compile(tokens, values) {
compiled.push(values[token.value]) compiled.push(values[token.value])
} else { } else {
if (process.env.NODE_ENV !== 'production') { if (process.env.NODE_ENV !== 'production') {
warn(`Type of token '${token.type}' and format of value '${mode}' don't match!`) console.warn(
`[svelte-i18n] Type of token '${
token.type
}' and format of value '${mode}' don't match!`,
)
} }
} }
break break
case 'unknown': case 'unknown':
if (process.env.NODE_ENV !== 'production') { if (process.env.NODE_ENV !== 'production') {
warn(`Detect 'unknown' type of token!`) console.warn(`[svelte-i18n] Detect 'unknown' type of token!`)
} }
break break
} }

View File

@ -1,12 +1,11 @@
import deepmerge from 'deepmerge'
import resolvePath from 'object-resolve-path'
import { capital, title, upper, lower } from './utils' import { capital, title, upper, lower } from './utils'
import Formatter from './formatter' import Formatter from './formatter'
const resolvePath = a => a export function i18n(store, { dictionary }) {
export function i18n(store, localesList) {
const formatter = new Formatter() const formatter = new Formatter()
const locales = {} // deepmerge.all(localesList) let dictionaries = {}
let currentLocale let currentLocale
const getLocalizedMessage = ( const getLocalizedMessage = (
@ -15,7 +14,7 @@ export function i18n(store, localesList) {
locale = currentLocale, locale = currentLocale,
transformers = undefined, transformers = undefined,
) => { ) => {
let message = resolvePath(locales[locale], path) let message = resolvePath(dictionaries[locale], path)
if (!message) return path if (!message) return path
@ -47,17 +46,32 @@ export function i18n(store, localesList) {
}, },
plural(path, counter, interpolations, locale) { plural(path, counter, interpolations, locale) {
return getLocalizedMessage(path, interpolations, locale, [ return getLocalizedMessage(path, interpolations, locale, [
message => message.split('|')[Math.min(Math.abs(counter), 2)], message => {
const choice =
typeof counter === 'number' ? Math.min(Math.abs(counter), 2) : 0
return message.split('|')[choice]
},
]) ])
}, },
} }
store.setLocale = locale => store.fire('locale', locale) store.i18n = {
setLocale(locale) {
store.fire('locale', locale)
},
extendDictionary(...list) {
dictionaries = deepmerge.all([dictionaries, ...list])
},
}
store.i18n.extendDictionary(dictionary)
store.on('locale', newLocale => { store.on('locale', newLocale => {
currentLocale = newLocale currentLocale = newLocale
const _ = getLocalizedMessage const _ = getLocalizedMessage
Object.assign(_, utilities) Object.assign(_, utilities)
store.set({ locale: newLocale, _ }) store.set({ locale: newLocale, _ })
}) })

View File

@ -1,15 +1,6 @@
export const capital = (str) => str.replace(/(^|\s)\S/, l => l.toUpperCase()) 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 title = str => str.replace(/(^|\s)\S/g, l => l.toUpperCase())
export const upper = (str) => str.toLocaleUpperCase() export const upper = str => str.toLocaleUpperCase()
export const lower = (str) => str.toLocaleLowerCase() export const lower = str => str.toLocaleLowerCase()
export const isObject = (obj) => obj !== null && typeof obj === 'object' export const isObject = obj => obj !== null && typeof obj === 'object'
export function warn(msg, err) {
if (typeof console !== 'undefined') {
console.warn(`[svelte-i18n] ${msg}`)
if (err) {
console.warn(err.stack)
}
}
}

View File

@ -1,6 +1,8 @@
// TODO: A more serious test
import { i18n } from '../src/index' import { i18n } from '../src/index'
import { Store } from 'svelte/store.umd' import { Store } from 'svelte/store.umd'
import { capital, title, upper, lower, isObject } from '../src/utils' import { capital, title, upper, lower, isObject, warn } from '../src/utils'
const store = new Store() const store = new Store()
const locales = { const locales = {
@ -11,31 +13,23 @@ const locales = {
pluralization: 'Zero | Um | Muito!', pluralization: 'Zero | Um | Muito!',
interpolation: { interpolation: {
key: 'Olá, {0}! Como está {1}?', key: 'Olá, {0}! Como está {1}?',
named: 'Olá, {name}! Como está {time}?' named: 'Olá, {name}! Como está {time}?',
}, },
wow: { wow: {
much: { much: {
deep: { deep: {
list: ['Muito', 'muito profundo'] list: ['Muito', 'muito profundo'],
} },
} },
}, },
obj: { obj: {
a: 'a' a: 'a',
} b: 'b',
} },
},
} }
i18n(store, [ i18n(store, { dictionary: locales })
locales,
{
'pt-br': {
obj: {
b: 'b'
}
}
}
])
/** /**
* Dummy test * Dummy test
@ -62,18 +56,18 @@ describe('Localization', () => {
expect(_).toBeInstanceOf(Function) expect(_).toBeInstanceOf(Function)
}) })
it('should have a .setLocale() method', () => { it('should have a .i18n.setLocale() method', () => {
expect(store.setLocale).toBeInstanceOf(Function) expect(store.i18n.setLocale).toBeInstanceOf(Function)
store.setLocale('pt-br') store.i18n.setLocale('pt-br')
const { locale } = store.get() const { locale } = store.get()
expect(locale).toBe('pt-br') expect(locale).toBe('pt-br')
}) })
it('should return the message id when no message identified by it was found', () => { it('should return the message id when no message identified by it was found', () => {
store.setLocale('pt-br') store.i18n.setLocale('pt-br')
const { locale, _ } = store.get() const { _ } = store.get()
expect(_('non.existent')).toBe('non.existent') expect(_('non.existent')).toBe('non.existent')
}) })
@ -84,15 +78,15 @@ describe('Localization', () => {
}) })
it('should get a deep nested message by its string path', () => { it('should get a deep nested message by its string path', () => {
store.setLocale('pt-br') store.i18n.setLocale('pt-br')
const { locale, _ } = store.get() const { _ } = store.get()
expect(_('obj.b')).toBe('b') expect(_('obj.b')).toBe('b')
}) })
it('should get a message within an array by its index', () => { it('should get a message within an array by its index', () => {
store.setLocale('pt-br') store.i18n.setLocale('pt-br')
const { locale, _ } = store.get() const { _ } = store.get()
expect(_('phrases[1]')).toBe(locales['pt-br'].phrases[1]) expect(_('phrases[1]')).toBe(locales['pt-br'].phrases[1])
@ -101,28 +95,31 @@ describe('Localization', () => {
}) })
it('should interpolate with {numeric} placeholders', () => { it('should interpolate with {numeric} placeholders', () => {
store.setLocale('pt-br') store.i18n.setLocale('pt-br')
const { locale, _ } = store.get() const { _ } = store.get()
expect(_('interpolation.key', ['Chris', 'o dia'])).toBe('Olá, Chris! Como está o dia?') expect(_('interpolation.key', ['Chris', 'o dia'])).toBe(
'Olá, Chris! Como está o dia?',
)
}) })
it('should interpolate with {named} placeholders', () => { it('should interpolate with {named} placeholders', () => {
store.setLocale('pt-br') store.i18n.setLocale('pt-br')
const { locale, _ } = store.get() const { _ } = store.get()
expect( expect(
_('interpolation.named', { _('interpolation.named', {
name: 'Chris', name: 'Chris',
time: 'o dia' time: 'o dia',
}) }),
).toBe('Olá, Chris! Como está o dia?') ).toBe('Olá, Chris! Como está o dia?')
}) })
it('should handle pluralization with _.plural()', () => { it('should handle pluralization with _.plural()', () => {
store.setLocale('pt-br') store.i18n.setLocale('pt-br')
const { locale, _ } = store.get() const { _ } = store.get()
expect(_.plural('pluralization')).toBe('Zero')
expect(_.plural('pluralization', 0)).toBe('Zero') expect(_.plural('pluralization', 0)).toBe('Zero')
expect(_.plural('pluralization', 1)).toBe('Um') expect(_.plural('pluralization', 1)).toBe('Um')
expect(_.plural('pluralization', -1)).toBe('Um') expect(_.plural('pluralization', -1)).toBe('Um')
@ -134,7 +131,7 @@ describe('Localization', () => {
describe('Localization utilities', () => { describe('Localization utilities', () => {
it('should capital a translated message', () => { it('should capital a translated message', () => {
store.setLocale('pt-br') store.i18n.setLocale('pt-br')
const { _ } = store.get() const { _ } = store.get()
expect(capital('Adoro banana')).toBe('Adoro banana') expect(capital('Adoro banana')).toBe('Adoro banana')
@ -142,7 +139,7 @@ describe('Localization utilities', () => {
}) })
it('should title a translated message', () => { it('should title a translated message', () => {
store.setLocale('pt-br') store.i18n.setLocale('pt-br')
const { _ } = store.get() const { _ } = store.get()
expect(title('Adoro Banana')).toBe('Adoro Banana') expect(title('Adoro Banana')).toBe('Adoro Banana')
@ -150,7 +147,7 @@ describe('Localization utilities', () => {
}) })
it('should lowercase a translated message', () => { it('should lowercase a translated message', () => {
store.setLocale('pt-br') store.i18n.setLocale('pt-br')
const { _ } = store.get() const { _ } = store.get()
expect(lower('adoro banana')).toBe('adoro banana') expect(lower('adoro banana')).toBe('adoro banana')
@ -158,7 +155,7 @@ describe('Localization utilities', () => {
}) })
it('should uppercase a translated message', () => { it('should uppercase a translated message', () => {
store.setLocale('pt-br') store.i18n.setLocale('pt-br')
const { _ } = store.get() const { _ } = store.get()
expect(upper('ADORO BANANA')).toBe('ADORO BANANA') expect(upper('ADORO BANANA')).toBe('ADORO BANANA')