mirror of
https://github.com/cupcakearmy/svelte-i18n.git
synced 2024-06-30 18:54:49 +02:00
Support svelte v3 (#6)
This commit is contained in:
parent
cec700af53
commit
0ae0cc8148
|
@ -3,4 +3,5 @@
|
||||||
/yarn.lock
|
/yarn.lock
|
||||||
/package-lock.json
|
/package-lock.json
|
||||||
/src
|
/src
|
||||||
/coverage
|
/coverage
|
||||||
|
/example
|
280
README.md
280
README.md
|
@ -4,132 +4,224 @@
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
### On the `store`
|
`svelte-i18n` utilizes svelte `stores` for keeping track of the current locale, dictionary of messages and the main format function. This way, we keep everything neat, in sync and easy to use on your svelte files.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Locale
|
||||||
|
|
||||||
|
The `locale` store defines what is the current locale.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import { i18n } from 'svelte-i18n'
|
import { locale, dictionary } from 'svelte-i18n'
|
||||||
import { Store } from 'svelte/store'
|
|
||||||
|
|
||||||
/** i18n(svelteStore, { dictionary }) */
|
// Set the current locale to en-US
|
||||||
let store = new Store()
|
locale.set('en-US')
|
||||||
|
|
||||||
store = i18n(store, {
|
// This is a store, so we can subscribe to its changes
|
||||||
dictionary: {
|
locale.subscribe(() => {
|
||||||
'pt-BR': {
|
console.log('locale change')
|
||||||
message: 'Mensagem',
|
})
|
||||||
greeting: 'Olá {name}, como vai?',
|
```
|
||||||
greetingIndex: 'Olá {0}, como vai?',
|
|
||||||
meter: 'metros | metro | metros',
|
---
|
||||||
book: 'livro | livros',
|
|
||||||
messages: {
|
### The dictionary
|
||||||
alert: 'Alerta',
|
|
||||||
error: 'Erro',
|
The `dictionary` store defines the dictionary of messages of all locales.
|
||||||
},
|
|
||||||
|
```js
|
||||||
|
import { locale, dictionary } from 'svelte-i18n'
|
||||||
|
|
||||||
|
// Define a locale dictionary
|
||||||
|
dictionary.set({
|
||||||
|
pt: {
|
||||||
|
message: 'Mensagem',
|
||||||
|
'switch.lang': 'Trocar idioma',
|
||||||
|
greeting: {
|
||||||
|
ask: 'Por favor, digite seu nome',
|
||||||
|
message: 'Olá {name}, como vai?',
|
||||||
},
|
},
|
||||||
'en-US': {
|
photos:
|
||||||
message: 'Message',
|
'Você {n, plural, =0 {não tem fotos.} =1 {tem uma foto.} other {tem # fotos.}}',
|
||||||
greeting: 'Hello {name}, how are you?',
|
cats: 'Tenho {n, number} {n,plural,=0{gatos}one{gato}other{gatos}}',
|
||||||
greetingIndex: 'Hello {0}, how are you?',
|
},
|
||||||
meter: 'meters | meter | meters',
|
en: {
|
||||||
book: 'book | books',
|
message: 'Message',
|
||||||
messages: {
|
'switch.lang': 'Switch language',
|
||||||
alert: 'Alert',
|
greeting: {
|
||||||
error: 'Error',
|
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}}',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
// It's also possible to merge the current dictionary
|
||||||
* Extend the initial dictionary.
|
// with other objets
|
||||||
* Dictionaries are deeply merged.
|
dictionary.update(dict => {
|
||||||
* */
|
dict.fr = {
|
||||||
store.i18n.extendDictionary({
|
// ...french messages
|
||||||
'pt-BR': {
|
}
|
||||||
messages: {
|
return dict
|
||||||
warn: 'Aviso',
|
|
||||||
success: 'Sucesso',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'en-US': {
|
|
||||||
messages: {
|
|
||||||
warn: 'Warn',
|
|
||||||
success: 'Success',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
/** Set the initial locale */
|
|
||||||
store.i18n.setLocale('en-US')
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### On `templates`
|
Each language message dictionary can be as deep as you want. Messages can also be looked up by a string represetation of it's path on the dictionary (i.e `greeting.message`).
|
||||||
|
|
||||||
#### Basic usage
|
---
|
||||||
|
|
||||||
|
### Formatting
|
||||||
|
|
||||||
|
The `_`/`format` store is the actual formatter method. To use it, it's simple as any other svelte store.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script>
|
||||||
|
// locale is en
|
||||||
|
import { _ } from 'svelte-i18n'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<input placeholder="{$_('greeting.ask')}" />
|
||||||
|
```
|
||||||
|
|
||||||
|
`svelte-i18n` uses `formatjs` behind the scenes, which means it supports the [ICU message format](http://userguide.icu-project.org/formatparse/messages) for interpolation, pluralization and much more.
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<div>
|
<div>
|
||||||
{$_('message')}: {$_('messages.success')}
|
{$_('greeting.message', { name: 'John' })}
|
||||||
<!-- Message: SUCCESS-->
|
<!-- Hello John, how are you? -->
|
||||||
|
|
||||||
|
{$_('photos', { n: 0 })}
|
||||||
|
<!-- You have no photos. -->
|
||||||
|
|
||||||
|
{$_('photos', { n: 12 })}
|
||||||
|
<!-- You have 12 photos. -->
|
||||||
</div>
|
</div>
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Current locale
|
### Formatting methods
|
||||||
|
|
||||||
The current locale is available via `this.store.get().locale`.
|
#### `_` / `format`
|
||||||
|
|
||||||
#### Interpolation
|
`function(messageId: string, locale:? string): string`
|
||||||
|
|
||||||
|
`function(messageId: string, interpolations?: object, locale:? string): string`
|
||||||
|
|
||||||
|
Main formatting method that formats a localized message by its id.
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<div>
|
<script>
|
||||||
<!-- Named interpolation -->
|
import { _ } from 'svelte-i18n'
|
||||||
{$_('greeting', { name: 'John' })}
|
</script>
|
||||||
<!-- Hello John, how are you?-->
|
|
||||||
|
|
||||||
<!-- List interpolation -->
|
<div>{$_('greeting.ask')}</div>
|
||||||
{$_('greetingIndex', ['John'])}
|
<!-- Please type your name -->
|
||||||
<!-- Hello John, how are you?-->
|
|
||||||
</div>
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Pluralization
|
#### `_.upper`
|
||||||
|
|
||||||
|
Transforms a localized message into uppercase.
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<div>
|
<script>
|
||||||
0 {$_.plural('meter', 0)}
|
import { _ } from 'svelte-i18n'
|
||||||
<!-- 0 meters -->
|
</script>
|
||||||
|
|
||||||
1 {$_.plural('meter', 1)}
|
<div>{$_.upper('greeting.ask')}</div>
|
||||||
<!-- 1 meter -->
|
<!-- PLEASE TYPE YOUR NAME -->
|
||||||
|
|
||||||
100 {$_.plural('meter', 100)}
|
|
||||||
<!-- 100 meters -->
|
|
||||||
|
|
||||||
0 {$_.plural('book', 0)}
|
|
||||||
<!-- 0 books -->
|
|
||||||
|
|
||||||
1 {$_.plural('book', 1)}
|
|
||||||
<!-- 1 book -->
|
|
||||||
|
|
||||||
10 {$_.plural('book', 10)}
|
|
||||||
<!-- 10 books -->
|
|
||||||
</div>
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Utilities
|
#### `_.lower`
|
||||||
|
|
||||||
|
Transforms a localized message into lowercase.
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<div>
|
<script>
|
||||||
{$_.upper('message')}
|
import { _ } from 'svelte-i18n'
|
||||||
<!-- MESSAGE -->
|
</script>
|
||||||
|
|
||||||
{$_.lower('message')}
|
<div>{$_.lower('greeting.ask')}</div>
|
||||||
<!-- message -->
|
<!-- PLEASE TYPE YOUR NAME -->
|
||||||
|
```
|
||||||
{$_.capital('message')}
|
|
||||||
<!-- Message -->
|
#### `_.capital`
|
||||||
|
|
||||||
{$_.title('greeting', { name: 'John' })}
|
Capitalize a localized message.
|
||||||
<!-- Hello John, How Are You?-->
|
|
||||||
</div>
|
```html
|
||||||
|
<script>
|
||||||
|
import { _ } from 'svelte-i18n'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>{$_.capital('greeting.ask')}</div>
|
||||||
|
<!-- Please type your name -->
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `_.title`
|
||||||
|
|
||||||
|
Transform the message into title case.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script>
|
||||||
|
import { _ } from 'svelte-i18n'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>{$_.capital('greeting.ask')}</div>
|
||||||
|
<!-- Please Type Your Name -->
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `_.time`
|
||||||
|
|
||||||
|
`function(time: Date, format?: string, locale?: string)`
|
||||||
|
|
||||||
|
Formats a date object into a time string with the specified format (`short`, `medium`, `long`, `full`). Please refer to the [ICU message format](http://userguide.icu-project.org/formatparse/messages) documentation for all available. formats
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script>
|
||||||
|
import { _ } from 'svelte-i18n'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>{$_.time(new Date(2019, 3, 24, 23, 45))}</div>
|
||||||
|
<!-- 11:45 PM -->
|
||||||
|
|
||||||
|
<div>{$_.time(new Date(2019, 3, 24, 23, 45), 'medium')}</div>
|
||||||
|
<!-- 11:45:00 PM -->
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `_.date`
|
||||||
|
|
||||||
|
`function(date: Date, format?: string, locale?: string)`
|
||||||
|
|
||||||
|
Formats a date object into a string with the specified format (`short`, `medium`, `long`, `full`). Please refer to the [ICU message format](http://userguide.icu-project.org/formatparse/messages) documentation for all available. formats
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script>
|
||||||
|
import { _ } from 'svelte-i18n'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>{$_.date(new Date(2019, 3, 24, 23, 45))}</div>
|
||||||
|
<!-- 4/24/19 -->
|
||||||
|
|
||||||
|
<div>{$_.date(new Date(2019, 3, 24, 23, 45), 'medium')}</div>
|
||||||
|
<!-- Apr 24, 2019 -->
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `_.number`
|
||||||
|
|
||||||
|
`function(number: Number, locale?: string)`
|
||||||
|
|
||||||
|
Formats a number with the specified locale
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script>
|
||||||
|
import { _ } from 'svelte-i18n'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>{$_.number(100000000)}</div>
|
||||||
|
<!-- 100,000,000 -->
|
||||||
|
|
||||||
|
<div>{$_.number(100000000, 'pt')}</div>
|
||||||
|
<!-- 100.000.000 -->
|
||||||
```
|
```
|
||||||
|
|
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
|
||||||
|
```
|
2924
example/package-lock.json
generated
Normal file
2924
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": "^1.0.2-beta"
|
||||||
|
}
|
||||||
|
}
|
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: 'I have {n, number} {n,plural,one{cat}other{cats}}'
|
||||||
|
},
|
||||||
|
})
|
9
example/src/main.js
Normal file
9
example/src/main.js
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import './i18n.js'
|
||||||
|
import App from './App.svelte'
|
||||||
|
|
||||||
|
const app = new App({
|
||||||
|
target: document.body,
|
||||||
|
props: { name: 'world' },
|
||||||
|
})
|
||||||
|
|
||||||
|
export default app
|
12517
package-lock.json
generated
12517
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
45
package.json
45
package.json
|
@ -1,9 +1,9 @@
|
||||||
{
|
{
|
||||||
"name": "svelte-i18n",
|
"name": "svelte-i18n",
|
||||||
"version": "0.0.5",
|
"version": "1.0.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "dist/i18n.js",
|
"main": "dist/i18n.js",
|
||||||
"module": "dist/i18n.m.js",
|
"module": "dist/i18n.mjs",
|
||||||
"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>",
|
||||||
|
@ -16,13 +16,12 @@
|
||||||
"translation"
|
"translation"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "microbundle --format=cjs,es",
|
"build": "microbundle --format=cjs,es --no-sourcemap",
|
||||||
"start": "microbundle watch --format=cjs,es",
|
"start": "microbundle watch --format=cjs,es",
|
||||||
"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 \"src/**/*.js\"",
|
"lint": "eslint \"src/**/*.js\"",
|
||||||
"format": "prettier --loglevel silent --write \"src/**/*.js\" && eslint --fix \"src/**/*.js\"",
|
"format": "prettier --loglevel silent --write \"src/**/*.js\" && eslint --fix \"src/**/*.js\""
|
||||||
"prepublishOnly": "npm run format && npm run test && npm run build"
|
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
"verbose": true,
|
"verbose": true,
|
||||||
|
@ -50,28 +49,30 @@
|
||||||
"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-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
|
|
||||||
}
|
|
159
src/index.js
159
src/index.js
|
@ -1,100 +1,71 @@
|
||||||
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 memoize from 'micro-memoize'
|
||||||
|
import { capital, title, upper, lower } from './utils.js'
|
||||||
|
|
||||||
export { capital, title, upper, lower }
|
let currentLocale
|
||||||
|
let currentDictionary
|
||||||
|
|
||||||
export function i18n(store, { dictionary: initialDictionary }) {
|
const getMessageFormatter = memoize(
|
||||||
const formatter = new Formatter()
|
(message, locale, formats) => new IntlMessageFormat(message, locale, formats),
|
||||||
let currentLocale
|
)
|
||||||
let dictionary = Array.isArray(initialDictionary)
|
|
||||||
? deepmerge.all(initialDictionary)
|
|
||||||
: initialDictionary
|
|
||||||
|
|
||||||
const getLocalizedMessage = (
|
const lookupMessage = memoize((path, locale) => {
|
||||||
path,
|
return (
|
||||||
interpolations,
|
currentDictionary[locale][path] ||
|
||||||
locale = currentLocale,
|
resolvePath(currentDictionary[locale], path)
|
||||||
transformers = undefined,
|
)
|
||||||
) => {
|
})
|
||||||
let message = resolvePath(dictionary[locale], path)
|
|
||||||
|
|
||||||
if (!message) return path
|
const formatMessage = (message, interpolations, locale = currentLocale) => {
|
||||||
|
return getMessageFormatter(message, locale).format(interpolations)
|
||||||
if (transformers) {
|
|
||||||
for (let i = 0, len = transformers.length; i < len; i++) {
|
|
||||||
message = transformers[i](message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getLocalizedMessage = (path, interpolations, locale = currentLocale) => {
|
||||||
|
if (typeof interpolations === 'string') {
|
||||||
|
locale = interpolations
|
||||||
|
interpolations = undefined
|
||||||
|
}
|
||||||
|
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 +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'
|
|
|
@ -1,182 +1,110 @@
|
||||||
// TODO: A more serious test
|
import { dictionary, locale, format } from '../src/index'
|
||||||
|
import { capital, title, upper, lower } from '../src/utils'
|
||||||
|
|
||||||
import { i18n } from '../src/index'
|
let _
|
||||||
import { Store } from 'svelte/store.umd'
|
let currentLocale
|
||||||
import { capital, title, upper, lower, isObject } from '../src/utils'
|
|
||||||
|
|
||||||
let store = new Store()
|
const dict = {
|
||||||
const locales = {
|
pt: {
|
||||||
'pt-br': {
|
hi: 'olá você',
|
||||||
test: 'teste',
|
'switch.lang': 'Trocar idioma',
|
||||||
phrase: 'adoro banana',
|
greeting: {
|
||||||
phrases: ['Frase 1', 'Frase 2'],
|
ask: 'Por favor, digite seu nome',
|
||||||
pluralization: 'Zero | Um | Muito!',
|
message: 'Olá {name}, como vai?',
|
||||||
simplePluralization: 'Singular | Plural',
|
|
||||||
interpolation: {
|
|
||||||
key: 'Olá, {0}! Como está {1}?',
|
|
||||||
named: 'Olá, {name}! Como está {time}?',
|
|
||||||
},
|
},
|
||||||
interpolationPluralization: 'One thingie | {0} thingies',
|
photos:
|
||||||
wow: {
|
'Você {n, plural, =0 {não tem fotos.} =1 {tem uma foto.} other {tem # fotos.}}',
|
||||||
much: {
|
cats: 'Tenho {n, number} {n,plural,=0{gatos}one{gato}other{gatos}}',
|
||||||
deep: {
|
},
|
||||||
list: ['Muito', 'muito profundo'],
|
en: {
|
||||||
},
|
hi: 'hi yo',
|
||||||
},
|
'switch.lang': 'Switch language',
|
||||||
},
|
greeting: {
|
||||||
obj: {
|
ask: 'Please type your name',
|
||||||
a: 'a',
|
message: 'Hello {name}, how are you?',
|
||||||
b: 'b',
|
|
||||||
},
|
},
|
||||||
|
photos:
|
||||||
|
'You have {n, plural, =0 {no photos.} =1 {one photo.} other {# photos.}}',
|
||||||
|
cats: 'I have {n, number} {n,plural,one{cat}other{cats}}',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
i18n(store, { dictionary: [locales] })
|
format.subscribe(formatFn => {
|
||||||
|
_ = formatFn
|
||||||
|
})
|
||||||
|
dictionary.set(dict)
|
||||||
|
locale.subscribe(l => (currentLocale = l))
|
||||||
|
locale.set('pt')
|
||||||
|
|
||||||
describe('Utilities', () => {
|
it('should change locale', () => {
|
||||||
it('should check if a variable is an object', () => {
|
locale.set('pt')
|
||||||
expect(isObject({})).toBe(true)
|
expect(currentLocale).toBe('pt')
|
||||||
expect(isObject(1)).toBe(false)
|
locale.set('en')
|
||||||
})
|
expect(currentLocale).toBe('en')
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Localization', () => {
|
it('should fallback to message id if id is not found', () => {
|
||||||
beforeEach(() => {
|
expect(_('batatinha')).toBe('batatinha')
|
||||||
console.error = jest.fn()
|
|
||||||
})
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
console.error.mockRestore()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should start with a clean store', () => {
|
|
||||||
const { _, locale } = store.get()
|
|
||||||
expect(locale).toBeFalsy()
|
|
||||||
expect(_).toBeFalsy()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should change the locale after a "locale" store event', () => {
|
|
||||||
store.fire('locale', 'pt-br')
|
|
||||||
const { locale, _ } = store.get()
|
|
||||||
|
|
||||||
expect(locale).toBe('pt-br')
|
|
||||||
expect(_).toBeInstanceOf(Function)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should have a .i18n.setLocale() method', () => {
|
|
||||||
expect(store.i18n.setLocale).toBeInstanceOf(Function)
|
|
||||||
|
|
||||||
store.i18n.setLocale('pt-br')
|
|
||||||
const { locale } = store.get()
|
|
||||||
|
|
||||||
expect(locale).toBe('pt-br')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should handle nonexistent locale', () => {
|
|
||||||
expect(store.i18n.setLocale('foo'))
|
|
||||||
expect(console.error).toHaveBeenCalledTimes(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should return the message id when no message identified by it was found', () => {
|
|
||||||
store.i18n.setLocale('pt-br')
|
|
||||||
const { _ } = store.get()
|
|
||||||
|
|
||||||
expect(_('non.existent')).toBe('non.existent')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should get a message by its id', () => {
|
|
||||||
const { _ } = store.get()
|
|
||||||
expect(_('test')).toBe(locales['pt-br'].test)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should get a deep nested message by its string path', () => {
|
|
||||||
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.i18n.setLocale('pt-br')
|
|
||||||
const { _ } = store.get()
|
|
||||||
|
|
||||||
expect(_('phrases[1]')).toBe(locales['pt-br'].phrases[1])
|
|
||||||
|
|
||||||
/** Not found */
|
|
||||||
expect(_('phrases[2]')).toBe('phrases[2]')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should interpolate with {numeric} placeholders', () => {
|
|
||||||
store.i18n.setLocale('pt-br')
|
|
||||||
const { _ } = store.get()
|
|
||||||
|
|
||||||
expect(_('interpolation.key', ['Chris', 'o dia'])).toBe(
|
|
||||||
'Olá, Chris! Como está o dia?',
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should interpolate with {named} placeholders', () => {
|
|
||||||
store.i18n.setLocale('pt-br')
|
|
||||||
const { _ } = store.get()
|
|
||||||
|
|
||||||
expect(
|
|
||||||
_('interpolation.named', {
|
|
||||||
name: 'Chris',
|
|
||||||
time: 'o dia',
|
|
||||||
}),
|
|
||||||
).toBe('Olá, Chris! Como está o dia?')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should handle pluralization with _.plural()', () => {
|
|
||||||
store.i18n.setLocale('pt-br')
|
|
||||||
const { _ } = store.get()
|
|
||||||
|
|
||||||
expect(_.plural('simplePluralization')).toBe('Plural')
|
|
||||||
expect(_.plural('simplePluralization', 1)).toBe('Singular')
|
|
||||||
expect(_.plural('simplePluralization', 3)).toBe('Plural')
|
|
||||||
expect(_.plural('simplePluralization', -23)).toBe('Plural')
|
|
||||||
expect(_.plural('pluralization')).toBe('Zero')
|
|
||||||
expect(_.plural('pluralization', 0)).toBe('Zero')
|
|
||||||
expect(_.plural('pluralization', 1)).toBe('Um')
|
|
||||||
expect(_.plural('pluralization', -1)).toBe('Um')
|
|
||||||
expect(_.plural('pluralization', -1000)).toBe('Muito!')
|
|
||||||
expect(_.plural('pluralization', 2)).toBe('Muito!')
|
|
||||||
expect(_.plural('pluralization', 100)).toBe('Muito!')
|
|
||||||
expect(_.plural('interpolationPluralization', 1)).toBe('One thingie')
|
|
||||||
expect(_.plural('interpolationPluralization', 10, [10])).toBe('10 thingies')
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Localization utilities', () => {
|
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 translate to passed locale', () => {
|
||||||
|
expect(_('switch.lang', 'pt')).toBe('Trocar idioma')
|
||||||
|
expect(_('switch.lang', 'en')).toBe('Switch language')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should interpolate message with variables', () => {
|
||||||
|
expect(_('greeting.message', { name: 'Chris' })).toBe(
|
||||||
|
'Hello Chris, how are you?',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should interpolate message with variables according to passed locale', () => {
|
||||||
|
expect(_('greeting.message', { name: 'Chris' }, 'pt')).toBe(
|
||||||
|
'Olá Chris, como vai?',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('utilities', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
locale.set('en')
|
||||||
|
})
|
||||||
|
|
||||||
it('should capital a translated message', () => {
|
it('should capital a translated message', () => {
|
||||||
store.i18n.setLocale('pt-br')
|
expect(_.capital('hi')).toBe('Hi yo')
|
||||||
const { _ } = store.get()
|
|
||||||
|
|
||||||
expect(capital('Adoro banana')).toBe('Adoro banana')
|
|
||||||
expect(_.capital('phrase')).toBe('Adoro banana')
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should title a translated message', () => {
|
it('should title a translated message', () => {
|
||||||
store.i18n.setLocale('pt-br')
|
expect(_.title('hi')).toBe('Hi Yo')
|
||||||
const { _ } = store.get()
|
|
||||||
|
|
||||||
expect(title('Adoro Banana')).toBe('Adoro Banana')
|
|
||||||
expect(_.title('phrase')).toBe('Adoro Banana')
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should lowercase a translated message', () => {
|
it('should lowercase a translated message', () => {
|
||||||
store.i18n.setLocale('pt-br')
|
expect(_.lower('hi')).toBe('hi yo')
|
||||||
const { _ } = store.get()
|
|
||||||
|
|
||||||
expect(lower('adoro banana')).toBe('adoro banana')
|
|
||||||
expect(_.lower('phrase')).toBe('adoro banana')
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should uppercase a translated message', () => {
|
it('should uppercase a translated message', () => {
|
||||||
store.i18n.setLocale('pt-br')
|
expect(_.upper('hi')).toBe('HI YO')
|
||||||
const { _ } = store.get()
|
|
||||||
|
|
||||||
expect(upper('ADORO BANANA')).toBe('ADORO BANANA')
|
|
||||||
expect(_.upper('phrase')).toBe('ADORO BANANA')
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
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')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue
Block a user