mirror of
https://github.com/cupcakearmy/svelte-i18n.git
synced 2024-07-04 12:44:51 +02:00
WIP
This commit is contained in:
parent
cec700af53
commit
4323f56ff3
3
example/.gitignore
vendored
Normal file
3
example/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
public/bundle.*
|
68
example/README.md
Normal file
68
example/README.md
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
*Psst — looking for a shareable component template? Go here --> [sveltejs/component-template](https://github.com/sveltejs/component-template)*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# svelte app
|
||||||
|
|
||||||
|
This is a project template for [Svelte](https://svelte.dev) apps. It lives at https://github.com/sveltejs/template.
|
||||||
|
|
||||||
|
To create a new project based on this template using [degit](https://github.com/Rich-Harris/degit):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx degit sveltejs/template svelte-app
|
||||||
|
cd svelte-app
|
||||||
|
```
|
||||||
|
|
||||||
|
*Note that you will need to have [Node.js](https://nodejs.org) installed.*
|
||||||
|
|
||||||
|
|
||||||
|
## Get started
|
||||||
|
|
||||||
|
Install the dependencies...
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd svelte-app
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
...then start [Rollup](https://rollupjs.org):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Navigate to [localhost:5000](http://localhost:5000). You should see your app running. Edit a component file in `src`, save it, and reload the page to see your changes.
|
||||||
|
|
||||||
|
|
||||||
|
## Deploying to the web
|
||||||
|
|
||||||
|
### With [now](https://zeit.co/now)
|
||||||
|
|
||||||
|
Install `now` if you haven't already:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -g now
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, from within your project folder:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
now
|
||||||
|
```
|
||||||
|
|
||||||
|
As an alternative, use the [Now desktop client](https://zeit.co/download) and simply drag the unzipped project folder to the taskbar icon.
|
||||||
|
|
||||||
|
### With [surge](https://surge.sh/)
|
||||||
|
|
||||||
|
Install `surge` if you haven't already:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -g surge
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, from within your project folder:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
surge public
|
||||||
|
```
|
2879
example/package-lock.json
generated
Normal file
2879
example/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
25
example/package.json
Normal file
25
example/package.json
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"name": "svelte-app",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"build": "rollup -c",
|
||||||
|
"autobuild": "rollup -c -w",
|
||||||
|
"dev": "run-p start:dev autobuild",
|
||||||
|
"start": "sirv public",
|
||||||
|
"start:dev": "sirv public --dev"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"npm-run-all": "^4.1.5",
|
||||||
|
"rollup": "^1.10.1",
|
||||||
|
"rollup-plugin-commonjs": "^9.3.4",
|
||||||
|
"rollup-plugin-livereload": "^1.0.0",
|
||||||
|
"rollup-plugin-node-resolve": "^4.2.3",
|
||||||
|
"rollup-plugin-svelte": "^5.0.3",
|
||||||
|
"rollup-plugin-terser": "^4.0.4",
|
||||||
|
"sirv-cli": "^0.4.0",
|
||||||
|
"svelte": "^3.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"svelte-i18n": "../"
|
||||||
|
}
|
||||||
|
}
|
BIN
example/public/favicon.png
Normal file
BIN
example/public/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.1 KiB |
61
example/public/global.css
Normal file
61
example/public/global.css
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
html, body {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
color: #333;
|
||||||
|
margin: 0;
|
||||||
|
padding: 8px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: rgb(0,100,200);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:visited {
|
||||||
|
color: rgb(0,80,160);
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
input, button, select, textarea {
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
padding: 0.4em;
|
||||||
|
margin: 0 0 0.5em 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:disabled {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"] {
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:active {
|
||||||
|
background-color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:focus {
|
||||||
|
border-color: #666;
|
||||||
|
}
|
21
example/public/index.html
Normal file
21
example/public/index.html
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf8" />
|
||||||
|
<meta name="viewport" content="width=device-width" />
|
||||||
|
|
||||||
|
<title>Svelte app</title>
|
||||||
|
|
||||||
|
<link rel="icon" type="image/png" href="favicon.png" />
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://cdnjs.cloudflare.com/ajax/libs/tachyons/4.11.1/tachyons.css"
|
||||||
|
/>
|
||||||
|
<link rel="stylesheet" href="global.css" />
|
||||||
|
<link rel="stylesheet" href="bundle.css" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<script src="bundle.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
44
example/rollup.config.js
Normal file
44
example/rollup.config.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import svelte from 'rollup-plugin-svelte'
|
||||||
|
import resolve from 'rollup-plugin-node-resolve'
|
||||||
|
import commonjs from 'rollup-plugin-commonjs'
|
||||||
|
import livereload from 'rollup-plugin-livereload'
|
||||||
|
import { terser } from 'rollup-plugin-terser'
|
||||||
|
|
||||||
|
const production = !process.env.ROLLUP_WATCH
|
||||||
|
|
||||||
|
export default {
|
||||||
|
input: 'src/main.js',
|
||||||
|
output: {
|
||||||
|
sourcemap: true,
|
||||||
|
format: 'iife',
|
||||||
|
name: 'app',
|
||||||
|
file: 'public/bundle.js',
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
svelte({
|
||||||
|
// enable run-time checks when not in production
|
||||||
|
dev: !production,
|
||||||
|
// we'll extract any component CSS out into
|
||||||
|
// a separate file — better for performance
|
||||||
|
css: css => {
|
||||||
|
css.write('public/bundle.css')
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
// If you have external dependencies installed from
|
||||||
|
// npm, you'll most likely need these plugins. In
|
||||||
|
// some cases you'll need additional configuration —
|
||||||
|
// consult the documentation for details:
|
||||||
|
// https://github.com/rollup/rollup-plugin-commonjs
|
||||||
|
resolve(),
|
||||||
|
commonjs(),
|
||||||
|
|
||||||
|
// Watch the `public` directory and refresh the
|
||||||
|
// browser on changes when not in production
|
||||||
|
!production && livereload('public'),
|
||||||
|
|
||||||
|
// If we're building for production (npm run build
|
||||||
|
// instead of npm run dev), minify
|
||||||
|
production && terser(),
|
||||||
|
],
|
||||||
|
}
|
45
example/src/App.svelte
Normal file
45
example/src/App.svelte
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<script>
|
||||||
|
import { locale, _ } from 'svelte-i18n'
|
||||||
|
|
||||||
|
let name = ''
|
||||||
|
let pluralN = 2
|
||||||
|
let catsN = 992301
|
||||||
|
let date = new Date()
|
||||||
|
|
||||||
|
$: oppositeLocale = $locale === 'pt' ? 'en' : 'pt'
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
date = new Date()
|
||||||
|
}, 1000)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<input
|
||||||
|
class="w-100"
|
||||||
|
type="text"
|
||||||
|
placeholder={$_('greeting.ask')}
|
||||||
|
bind:value={name} />
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<h1>{$_.title('greeting.message', { name })}</h1>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<input type="range" min="0" max="5" step="1" bind:value={pluralN} />
|
||||||
|
<h2>Plural: {$_('photos', { n: pluralN })}</h2>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<input type="range" min="100" max="100000000" step="10000" bind:value={catsN} />
|
||||||
|
<h2>Number: {$_('cats', { n: catsN })}</h2>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<h2>Number util: {$_.number(catsN)}</h2>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<h2>Date util: {$_.date(date, 'short')}</h2>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<h2>Time util: {$_.time(date, 'medium')}</h2>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<button on:click={() => locale.set(oppositeLocale)}>
|
||||||
|
{$_('switch.lang', null, oppositeLocale)}
|
||||||
|
</button>
|
31
example/src/i18n.js
Normal file
31
example/src/i18n.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { locale, dictionary } from 'svelte-i18n'
|
||||||
|
|
||||||
|
// setting the locale
|
||||||
|
locale.set('pt')
|
||||||
|
|
||||||
|
// subscribe to locale changes
|
||||||
|
locale.subscribe(() => {
|
||||||
|
console.log('locale change')
|
||||||
|
})
|
||||||
|
|
||||||
|
// defining a locale dictionary
|
||||||
|
dictionary.set({
|
||||||
|
pt: {
|
||||||
|
'switch.lang': 'Trocar idioma',
|
||||||
|
greeting: {
|
||||||
|
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.}}',
|
||||||
|
cats: 'Tenho {n, number} {n,plural,=0{gatos}one{gato}other{gatos}}',
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
'switch.lang': 'Switch language',
|
||||||
|
greeting: {
|
||||||
|
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: '{n,plural,one{gato}other{gatos}}'
|
||||||
|
},
|
||||||
|
})
|
11
example/src/main.js
Normal file
11
example/src/main.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import App from './App.svelte';
|
||||||
|
import './i18n.js'
|
||||||
|
|
||||||
|
const app = new App({
|
||||||
|
target: document.body,
|
||||||
|
props: {
|
||||||
|
name: 'world'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default app;
|
2055
example/yarn.lock
Normal file
2055
example/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
12522
package-lock.json
generated
12522
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
39
package.json
39
package.json
@ -3,7 +3,7 @@
|
|||||||
"version": "0.0.5",
|
"version": "0.0.5",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "dist/i18n.js",
|
"main": "dist/i18n.js",
|
||||||
"module": "dist/i18n.m.js",
|
"module": "src/index.js",
|
||||||
"types": "src/index.d.ts",
|
"types": "src/index.d.ts",
|
||||||
"description": "Internationalization library for Svelte",
|
"description": "Internationalization library for Svelte",
|
||||||
"author": "Christian Kaisermann <christian@kaisermann.me>",
|
"author": "Christian Kaisermann <christian@kaisermann.me>",
|
||||||
@ -50,28 +50,31 @@
|
|||||||
"collectCoverage": true
|
"collectCoverage": true
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.0.0-beta.56",
|
"@babel/core": "^7.4.4",
|
||||||
"@babel/preset-env": "^7.0.0-beta.56",
|
"@babel/preset-env": "^7.4.4",
|
||||||
"babel-core": "^7.0.0-bridge.0",
|
"babel-core": "^7.0.0-bridge.0",
|
||||||
"babel-jest": "^23.4.2",
|
"babel-jest": "^24.8.0",
|
||||||
"eslint": "^5.3.0",
|
"eslint": "^5.16.0",
|
||||||
"eslint-config-prettier": "^2.9.0",
|
"eslint-config-prettier": "^4.3.0",
|
||||||
"eslint-config-standard": "^11.0.0",
|
"eslint-config-standard": "^12.0.0",
|
||||||
"eslint-plugin-import": "^2.13.0",
|
"eslint-plugin-import": "^2.17.2",
|
||||||
"eslint-plugin-node": "^7.0.1",
|
"eslint-plugin-node": "^9.0.1",
|
||||||
"eslint-plugin-prettier": "^2.6.2",
|
"eslint-plugin-prettier": "^3.1.0",
|
||||||
"eslint-plugin-promise": "^3.8.0",
|
"eslint-plugin-promise": "^4.1.1",
|
||||||
"eslint-plugin-standard": "^3.1.0",
|
"eslint-plugin-standard": "^4.0.0",
|
||||||
"jest": "^23.4.2",
|
"jest": "^24.8.0",
|
||||||
"microbundle": "^0.6.0",
|
"microbundle": "^0.11.0",
|
||||||
"prettier": "^1.14.1",
|
"prettier": "^1.17.1",
|
||||||
"svelte": "^2.9.11"
|
"svelte": "^3.4.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"svelte": "^2.9.11"
|
"svelte": "^3.4.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"deepmerge": "^2.1.1",
|
"deepmerge": "^3.2.0",
|
||||||
|
"intl-format-cache": "^2.1.0",
|
||||||
|
"intl-messageformat": "^2.2.0",
|
||||||
|
"micro-memoize": "^3.0.1",
|
||||||
"object-resolve-path": "^1.1.1"
|
"object-resolve-path": "^1.1.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
124
src/formatter.js
124
src/formatter.js
@ -1,124 +0,0 @@
|
|||||||
/**
|
|
||||||
* Adapted from 'https://github.com/kazupon/vue-i18n/blob/dev/src/format.js'
|
|
||||||
* Copyright (c) 2016 kazuya kawaguchi
|
|
||||||
**/
|
|
||||||
import { isObject } from './utils'
|
|
||||||
|
|
||||||
const RE_TOKEN_LIST_VALUE = /^(\d)+/
|
|
||||||
const RE_TOKEN_NAMED_VALUE = /^(\w)+/
|
|
||||||
|
|
||||||
export default class Formatter {
|
|
||||||
constructor() {
|
|
||||||
this._caches = Object.create(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
interpolate(message, values) {
|
|
||||||
if (!values) {
|
|
||||||
return [message]
|
|
||||||
}
|
|
||||||
|
|
||||||
let tokens = this._caches[message]
|
|
||||||
if (!tokens) {
|
|
||||||
tokens = parse(message)
|
|
||||||
this._caches[message] = tokens
|
|
||||||
}
|
|
||||||
|
|
||||||
return compile(tokens, values)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Parse a identification string into cached Tokens */
|
|
||||||
export function parse(format) {
|
|
||||||
const tokens = []
|
|
||||||
let position = 0
|
|
||||||
let currentText = ''
|
|
||||||
|
|
||||||
while (position < format.length) {
|
|
||||||
let char = format[position++]
|
|
||||||
|
|
||||||
/** If found any character that's not a '{' (does not include '\{'), assume text */
|
|
||||||
if (char !== '{' || (position > 0 && char[position - 1] === '\\')) {
|
|
||||||
currentText += char
|
|
||||||
} else {
|
|
||||||
/** Beginning of a interpolation */
|
|
||||||
if (currentText.length) {
|
|
||||||
tokens.push({ type: 'text', value: currentText })
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Reset the current text string because we're dealing interpolation entry */
|
|
||||||
currentText = ''
|
|
||||||
|
|
||||||
/** Key name */
|
|
||||||
let namedKey = ''
|
|
||||||
char = format[position++]
|
|
||||||
|
|
||||||
while (char !== '}') {
|
|
||||||
namedKey += char
|
|
||||||
char = format[position++]
|
|
||||||
}
|
|
||||||
|
|
||||||
const type = RE_TOKEN_LIST_VALUE.test(namedKey)
|
|
||||||
? 'list'
|
|
||||||
: RE_TOKEN_NAMED_VALUE.test(namedKey)
|
|
||||||
? 'named'
|
|
||||||
: 'unknown'
|
|
||||||
|
|
||||||
tokens.push({ value: namedKey, type })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** If there's any text left, push it to the tokens list */
|
|
||||||
if (currentText) {
|
|
||||||
tokens.push({ type: 'text', value: currentText })
|
|
||||||
}
|
|
||||||
|
|
||||||
return tokens
|
|
||||||
}
|
|
||||||
|
|
||||||
export function compile(tokens, values) {
|
|
||||||
const compiled = []
|
|
||||||
let index = 0
|
|
||||||
|
|
||||||
const mode = Array.isArray(values)
|
|
||||||
? 'list'
|
|
||||||
: isObject(values)
|
|
||||||
? 'named'
|
|
||||||
: 'unknown'
|
|
||||||
|
|
||||||
if (mode === 'unknown') {
|
|
||||||
return compiled
|
|
||||||
}
|
|
||||||
|
|
||||||
while (index < tokens.length) {
|
|
||||||
const token = tokens[index++]
|
|
||||||
switch (token.type) {
|
|
||||||
case 'text':
|
|
||||||
compiled.push(token.value)
|
|
||||||
break
|
|
||||||
case 'list':
|
|
||||||
compiled.push(values[parseInt(token.value, 10)])
|
|
||||||
break
|
|
||||||
case 'named':
|
|
||||||
if (mode === 'named') {
|
|
||||||
compiled.push(values[token.value])
|
|
||||||
} else {
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
|
||||||
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') {
|
|
||||||
console.warn(`[svelte-i18n] Detect 'unknown' type of token!`)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return compiled
|
|
||||||
}
|
|
158
src/index.js
158
src/index.js
@ -1,100 +1,70 @@
|
|||||||
import deepmerge from 'deepmerge'
|
import { writable, derived } from 'svelte/store'
|
||||||
import resolvePath from 'object-resolve-path'
|
import resolvePath from 'object-resolve-path'
|
||||||
import { capital, title, upper, lower } from './utils'
|
import IntlMessageFormat from 'intl-messageformat'
|
||||||
import Formatter from './formatter'
|
import memoizeConstructor from 'intl-format-cache'
|
||||||
|
|
||||||
export { capital, title, upper, lower }
|
const capital = str => str.replace(/(^|\s)\S/, l => l.toUpperCase())
|
||||||
|
const title = str => str.replace(/(^|\s)\S/g, l => l.toUpperCase())
|
||||||
|
const upper = str => str.toLocaleUpperCase()
|
||||||
|
const lower = str => str.toLocaleLowerCase()
|
||||||
|
|
||||||
export function i18n(store, { dictionary: initialDictionary }) {
|
let currentLocale
|
||||||
const formatter = new Formatter()
|
let currentDictionary
|
||||||
let currentLocale
|
|
||||||
let dictionary = Array.isArray(initialDictionary)
|
|
||||||
? deepmerge.all(initialDictionary)
|
|
||||||
: initialDictionary
|
|
||||||
|
|
||||||
const getLocalizedMessage = (
|
const getMessageFormatter = memoizeConstructor(IntlMessageFormat)
|
||||||
path,
|
|
||||||
interpolations,
|
|
||||||
locale = currentLocale,
|
|
||||||
transformers = undefined,
|
|
||||||
) => {
|
|
||||||
let message = resolvePath(dictionary[locale], path)
|
|
||||||
|
|
||||||
if (!message) return path
|
function lookupMessage(path, locale) {
|
||||||
|
// TODO improve perf here
|
||||||
if (transformers) {
|
return (
|
||||||
for (let i = 0, len = transformers.length; i < len; i++) {
|
currentDictionary[locale][path] ||
|
||||||
message = transformers[i](message)
|
resolvePath(currentDictionary[locale], path)
|
||||||
}
|
)
|
||||||
}
|
|
||||||
|
|
||||||
if (interpolations) {
|
|
||||||
message = formatter.interpolate(message, interpolations).join('')
|
|
||||||
}
|
|
||||||
|
|
||||||
return message.trim()
|
|
||||||
}
|
|
||||||
|
|
||||||
const utilities = {
|
|
||||||
capital(path, interpolations, locale) {
|
|
||||||
return capital(getLocalizedMessage(path, interpolations, locale))
|
|
||||||
},
|
|
||||||
title(path, interpolations, locale) {
|
|
||||||
return title(getLocalizedMessage(path, interpolations, locale))
|
|
||||||
},
|
|
||||||
upper(path, interpolations, locale) {
|
|
||||||
return upper(getLocalizedMessage(path, interpolations, locale))
|
|
||||||
},
|
|
||||||
lower(path, interpolations, locale) {
|
|
||||||
return lower(getLocalizedMessage(path, interpolations, locale))
|
|
||||||
},
|
|
||||||
plural(path, counter, interpolations, locale) {
|
|
||||||
return getLocalizedMessage(path, interpolations, locale, [
|
|
||||||
message => {
|
|
||||||
const parts = message.split('|')
|
|
||||||
|
|
||||||
/** Check for 'singular|plural' or 'zero|one|multiple' pluralization */
|
|
||||||
const isSimplePluralization = parts.length === 2
|
|
||||||
let choice = isSimplePluralization ? 1 : 0
|
|
||||||
|
|
||||||
if (typeof counter === 'number') {
|
|
||||||
choice = Math.min(
|
|
||||||
Math.abs(counter) - (isSimplePluralization ? 1 : 0),
|
|
||||||
parts.length - 1,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return parts[choice]
|
|
||||||
},
|
|
||||||
])
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
store.on('locale', newLocale => {
|
|
||||||
if (!Object.keys(dictionary).includes(newLocale)) {
|
|
||||||
console.error(`[svelte-i18n] Couldn't find the "${newLocale}" locale.`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
currentLocale = newLocale
|
|
||||||
const _ = getLocalizedMessage
|
|
||||||
|
|
||||||
_.upper = utilities.upper
|
|
||||||
_.lower = utilities.lower
|
|
||||||
_.title = utilities.title
|
|
||||||
_.capital = utilities.capital
|
|
||||||
_.plural = utilities.plural
|
|
||||||
|
|
||||||
store.set({ locale: newLocale, _ })
|
|
||||||
})
|
|
||||||
|
|
||||||
store.i18n = {
|
|
||||||
setLocale(locale) {
|
|
||||||
store.fire('locale', locale)
|
|
||||||
},
|
|
||||||
extendDictionary(...list) {
|
|
||||||
dictionary = deepmerge.all([dictionary, ...list])
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return store
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatMessage(message, interpolations, locale = currentLocale) {
|
||||||
|
return getMessageFormatter(message, locale).format(interpolations)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLocalizedMessage(path, interpolations, locale = currentLocale) {
|
||||||
|
const message = lookupMessage(path, locale)
|
||||||
|
|
||||||
|
if (!message) return path
|
||||||
|
if (!interpolations) return message
|
||||||
|
|
||||||
|
return getMessageFormatter(message, locale).format(interpolations)
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
const dictionary = writable({})
|
||||||
|
dictionary.subscribe(newDictionary => {
|
||||||
|
currentDictionary = newDictionary
|
||||||
|
})
|
||||||
|
|
||||||
|
const locale = writable({})
|
||||||
|
locale.subscribe(newLocale => {
|
||||||
|
currentLocale = newLocale
|
||||||
|
})
|
||||||
|
|
||||||
|
const format = derived(locale, () => getLocalizedMessage)
|
||||||
|
|
||||||
|
export { locale, format as _, format, dictionary }
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
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'
|
|
@ -1,11 +1,9 @@
|
|||||||
// TODO: A more serious test
|
// TODO: A more serious test
|
||||||
|
|
||||||
import { i18n } from '../src/index'
|
import { dictionary, locale } from '../src/index'
|
||||||
import { Store } from 'svelte/store.umd'
|
import { capital, title, upper, lower } from '../src/utils'
|
||||||
import { capital, title, upper, lower, isObject } from '../src/utils'
|
|
||||||
|
|
||||||
let store = new Store()
|
dictionary.set({
|
||||||
const locales = {
|
|
||||||
'pt-br': {
|
'pt-br': {
|
||||||
test: 'teste',
|
test: 'teste',
|
||||||
phrase: 'adoro banana',
|
phrase: 'adoro banana',
|
||||||
@ -29,17 +27,11 @@ const locales = {
|
|||||||
b: 'b',
|
b: 'b',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
|
||||||
|
|
||||||
i18n(store, { dictionary: [locales] })
|
|
||||||
|
|
||||||
describe('Utilities', () => {
|
|
||||||
it('should check if a variable is an object', () => {
|
|
||||||
expect(isObject({})).toBe(true)
|
|
||||||
expect(isObject(1)).toBe(false)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
locale.set('pt-br')
|
||||||
|
|
||||||
|
|
||||||
describe('Localization', () => {
|
describe('Localization', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
console.error = jest.fn()
|
console.error = jest.fn()
|
||||||
|
Loading…
Reference in New Issue
Block a user