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

@ -1,4 +1,5 @@
/test/
/.*
/yarn.lock
/package-lock.json
/package-lock.json
/src

View File

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

3676
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "svelte-i18n",
"version": "0.0.0",
"version": "0.0.1",
"license": "MIT",
"main": "dist/i18n.js",
"types": "src/index.d.ts",
@ -13,9 +13,9 @@
"start": "microbundle watch --name='svelte-i18n'",
"test": "jest --no-cache --verbose",
"test:watch": "jest --no-cache --verbose --watchAll",
"lint": "eslint \"*.js\" \"src/**/*.js\"",
"format": "prettier --loglevel silent --write \"*.js\" \"src/**/*.js\" && eslint --fix \"*.js\" \"src/**/*.js\"",
"prepublishOnly": "npm run format && npm run test"
"lint": "eslint \"src/**/*.js\"",
"format": "prettier --loglevel silent --write \"src/**/*.js\" && eslint --fix \"src/**/*.js\"",
"prepublishOnly": "npm run format && npm run test && npm run build"
},
"jest": {
"verbose": true,
@ -29,7 +29,8 @@
],
"coveragePathIgnorePatterns": [
"/node_modules/",
"/test/"
"/test/",
"/src/formatter.js"
],
"coverageThreshold": {
"global": {
@ -43,26 +44,26 @@
},
"devDependencies": {
"@babel/core": "^7.0.0-beta.56",
"@babel/preset-env": "^7.0.0-beta.56",
"babel-core": "^7.0.0-bridge.0",
"babel-jest": "^23.4.2",
"eslint": "^4.19.1",
"eslint": "^5.3.0",
"eslint-config-prettier": "^2.9.0",
"eslint-config-standard": "^11.0.0",
"eslint-plugin-import": "^2.11.0",
"eslint-plugin-node": "^6.0.1",
"eslint-plugin-prettier": "^2.6.0",
"eslint-plugin-promise": "^3.7.0",
"eslint-plugin-import": "^2.13.0",
"eslint-plugin-node": "^7.0.1",
"eslint-plugin-prettier": "^2.6.2",
"eslint-plugin-promise": "^3.8.0",
"eslint-plugin-standard": "^3.1.0",
"jest": "^22.4.3",
"microbundle": "^0.4.4",
"prettier": "^1.12.1",
"svelte": "^2.9.10"
"jest": "^23.4.2",
"microbundle": "^0.6.0",
"prettier": "^1.14.1",
"svelte": "^2.9.11"
},
"peerDependencies": {
"svelte": "^2.9.10"
"svelte": "^2.9.11"
},
"dependencies": {
"@babel/preset-env": "^7.0.0-beta.56",
"deepmerge": "^2.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'
* Copyright (c) 2016 kazuya kawaguchi
**/
import { isObject, warn } from './utils'
import { isObject } from './utils'
const RE_TOKEN_LIST_VALUE = /^(\d)+/
const RE_TOKEN_NAMED_VALUE = /^(\w)+/
@ -28,7 +29,7 @@ export default class Formatter {
}
/** Parse a identification string into cached Tokens */
export function parse(format){
export function parse(format) {
const tokens = []
let position = 0
let currentText = ''
@ -77,9 +78,13 @@ export function parse(format){
export function compile(tokens, values) {
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') {
return compiled
@ -99,14 +104,18 @@ export function compile(tokens, values) {
compiled.push(values[token.value])
} else {
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
case 'unknown':
if (process.env.NODE_ENV !== 'production') {
warn(`Detect 'unknown' type of token!`)
console.warn(`[svelte-i18n] Detect 'unknown' type of token!`)
}
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 Formatter from './formatter'
const resolvePath = a => a
export function i18n(store, localesList) {
export function i18n(store, { dictionary }) {
const formatter = new Formatter()
const locales = {} // deepmerge.all(localesList)
let dictionaries = {}
let currentLocale
const getLocalizedMessage = (
@ -15,7 +14,7 @@ export function i18n(store, localesList) {
locale = currentLocale,
transformers = undefined,
) => {
let message = resolvePath(locales[locale], path)
let message = resolvePath(dictionaries[locale], path)
if (!message) return path
@ -47,17 +46,32 @@ export function i18n(store, localesList) {
},
plural(path, counter, 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 => {
currentLocale = newLocale
const _ = getLocalizedMessage
Object.assign(_, utilities)
store.set({ locale: newLocale, _ })
})

View File

@ -1,15 +1,6 @@
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 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 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)
}
}
}
export const isObject = obj => obj !== null && typeof obj === 'object'

View File

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