├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .gitignore
├── .npmrc
├── .travis.yml
├── LICENSE
├── examples
├── example-bot.js
└── locales
│ ├── en-us.json
│ ├── en.yaml
│ └── ru.yaml
├── lib
├── context.js
├── i18n.js
├── index.d.ts
└── pluralize.js
├── package.json
├── readme.md
└── test
├── analyse-language-repository.js
├── basics.js
└── pluralize.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
15 | [*.txt]
16 | trim_trailing_whitespace = false
17 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["standard"],
3 | "plugins": ["ava"]
4 | }
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 |
17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
18 | .grunt
19 |
20 | # node-waf configuration
21 | .lock-wscript
22 |
23 | # Compiled binary addons (http://nodejs.org/api/addons.html)
24 | build/Release
25 |
26 | # Dependency directory
27 | node_modules
28 |
29 | # Optional npm cache directory
30 | .npm
31 |
32 | # Optional REPL history
33 | .node_repl_history
34 | package-lock.json
35 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | package-lock=false
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 8
4 | - 9
5 | - 10
6 | - 11
7 | - 12
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Vitaly Domnikov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/examples/example-bot.js:
--------------------------------------------------------------------------------
1 | const Telegraf = require('telegraf')
2 | const path = require('path')
3 | const I18n = require('../lib/i18n')
4 | const { Extra } = Telegraf
5 |
6 | // i18n options
7 | const i18n = new I18n({
8 | directory: path.resolve(__dirname, 'locales'),
9 | defaultLanguage: 'en',
10 | sessionName: 'session',
11 | useSession: true,
12 | templateData: {
13 | pluralize: I18n.pluralize,
14 | uppercase: (value) => value.toUpperCase()
15 | }
16 | })
17 |
18 | const bot = new Telegraf(process.env.BOT_TOKEN)
19 | bot.use(Telegraf.session())
20 | bot.use(i18n.middleware())
21 |
22 | // Start message handler
23 | bot.start(({ i18n, replyWithHTML }) => replyWithHTML(i18n.t('greeting')))
24 |
25 | // Using i18n helpers
26 | bot.command('help', I18n.reply('greeting', Extra.HTML()))
27 |
28 | // Set locale to `en`
29 | bot.command('en', ({ i18n, replyWithHTML }) => {
30 | i18n.locale('en-US')
31 | return replyWithHTML(i18n.t('greeting'))
32 | })
33 |
34 | // Set locale to `ru`
35 | bot.command('ru', ({ i18n, replyWithHTML }) => {
36 | i18n.locale('ru')
37 | return replyWithHTML(i18n.t('greeting'))
38 | })
39 |
40 | // Add apple to cart
41 | bot.command('add', ({ session, i18n, reply }) => {
42 | session.apples = session.apples || 0
43 | session.apples++
44 | const message = i18n.t('cart', { apples: session.apples })
45 | return reply(message)
46 | })
47 |
48 | // Add apple to cart
49 | bot.command('cart', (ctx) => {
50 | const message = ctx.i18n.t('cart', { apples: ctx.session.apples || 0 })
51 | return ctx.reply(message)
52 | })
53 |
54 | // Checkout
55 | bot.command('checkout', ({ reply, i18n }) => reply(i18n.t('checkout')))
56 | bot.startPolling()
57 |
--------------------------------------------------------------------------------
/examples/locales/en-us.json:
--------------------------------------------------------------------------------
1 | {
2 | "checkout": "Yo!"
3 | }
4 |
--------------------------------------------------------------------------------
/examples/locales/en.yaml:
--------------------------------------------------------------------------------
1 | greeting: Hello ${uppercase(from.first_name)}!
2 | cart: ${from.first_name}, in your cart ${pluralize(apples, 'apple', 'apples')}
3 | checkout: Thank you
4 |
--------------------------------------------------------------------------------
/examples/locales/ru.yaml:
--------------------------------------------------------------------------------
1 | greeting: Привет ${uppercase(from.first_name)}!
2 | cart: В вашей корзине ${pluralize(apples, 'яблоко', 'яблокa', 'яблок')}
3 |
--------------------------------------------------------------------------------
/lib/context.js:
--------------------------------------------------------------------------------
1 | class I18nContext {
2 | constructor (repository, config, languageCode, templateData) {
3 | this.repository = repository
4 | this.config = config
5 | this.locale(languageCode || config.defaultLanguage)
6 | this.templateData = {
7 | ...config.templateData,
8 | ...templateData
9 | }
10 | }
11 |
12 | locale (languageCode) {
13 | if (!languageCode) {
14 | return this.languageCode
15 | }
16 |
17 | const code = languageCode.toLowerCase()
18 | const shortCode = code.split('-')[0]
19 |
20 | if (!this.repository[code] && !this.repository[shortCode]) {
21 | this.languageCode = this.config.defaultLanguage
22 | this.shortLanguageCode = this.languageCode.split('-')[0]
23 | return
24 | }
25 |
26 | this.languageCode = code
27 | this.shortLanguageCode = shortCode
28 | }
29 |
30 | getTemplate (languageCode, resourceKey = '') {
31 | return resourceKey
32 | .split('.')
33 | .reduce((acc, key) => acc && acc[key], this.repository[languageCode])
34 | }
35 |
36 | t (resourceKey, templateData) {
37 | let template = this.getTemplate(this.languageCode, resourceKey) || this.getTemplate(this.shortLanguageCode, resourceKey)
38 |
39 | if (!template && this.config.defaultLanguageOnMissing) {
40 | template = this.getTemplate(this.config.defaultLanguage, resourceKey)
41 | }
42 |
43 | if (!template && this.config.allowMissing) {
44 | template = () => resourceKey
45 | }
46 |
47 | if (!template) {
48 | throw new Error(`telegraf-i18n: '${this.languageCode}.${resourceKey}' not found`)
49 | }
50 | const context = {
51 | ...this.templateData,
52 | ...templateData
53 | }
54 | Object.keys(context)
55 | .filter((key) => typeof context[key] === 'function')
56 | .forEach((key) => (context[key] = context[key].bind(this)))
57 | return template(context)
58 | }
59 | }
60 |
61 | module.exports = I18nContext
62 |
--------------------------------------------------------------------------------
/lib/i18n.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const compile = require('compile-template')
3 | const yaml = require('js-yaml')
4 | const path = require('path')
5 | const I18nContext = require('./context.js')
6 | const pluralize = require('./pluralize.js')
7 |
8 | class I18n {
9 | constructor (config = {}) {
10 | this.repository = {}
11 | this.config = {
12 | defaultLanguage: 'en',
13 | sessionName: 'session',
14 | allowMissing: true,
15 | templateData: {
16 | pluralize
17 | },
18 | ...config
19 | }
20 | if (this.config.directory) {
21 | this.loadLocales(this.config.directory)
22 | }
23 | }
24 |
25 | loadLocales (directory) {
26 | if (!fs.existsSync(directory)) {
27 | throw new Error(`Locales directory '${directory}' not found`)
28 | }
29 | const files = fs.readdirSync(directory)
30 | files.forEach((fileName) => {
31 | const extension = path.extname(fileName)
32 | const languageCode = path.basename(fileName, extension).toLowerCase()
33 | if (extension === '.yaml' || extension === '.yml') {
34 | const data = fs.readFileSync(path.resolve(directory, fileName), 'utf8')
35 | this.loadLocale(languageCode, yaml.safeLoad(data))
36 | } else if (extension === '.json') {
37 | this.loadLocale(languageCode, require(path.resolve(directory, fileName)))
38 | }
39 | })
40 | }
41 |
42 | loadLocale (languageCode, i18Data) {
43 | const language = languageCode.toLowerCase()
44 | this.repository[language] = {
45 | ...this.repository[language],
46 | ...compileTemplates(i18Data)
47 | }
48 | }
49 |
50 | resetLocale (languageCode) {
51 | if (languageCode) {
52 | delete this.repository[languageCode.toLowerCase()]
53 | } else {
54 | this.repository = {}
55 | }
56 | }
57 |
58 | availableLocales () {
59 | return Object.keys(this.repository)
60 | }
61 |
62 | resourceKeys (languageCode) {
63 | const language = languageCode.toLowerCase()
64 | return getTemplateKeysRecursive(this.repository[language] || {})
65 | }
66 |
67 | missingKeys (languageOfInterest, referenceLanguage = this.config.defaultLanguage) {
68 | const interest = this.resourceKeys(languageOfInterest)
69 | const reference = this.resourceKeys(referenceLanguage)
70 |
71 | return reference.filter((ref) => !interest.includes(ref))
72 | }
73 |
74 | overspecifiedKeys (languageOfInterest, referenceLanguage = this.config.defaultLanguage) {
75 | return this.missingKeys(referenceLanguage, languageOfInterest)
76 | }
77 |
78 | translationProgress (languageOfInterest, referenceLanguage = this.config.defaultLanguage) {
79 | const reference = this.resourceKeys(referenceLanguage).length
80 | const missing = this.missingKeys(languageOfInterest, referenceLanguage).length
81 |
82 | return (reference - missing) / reference
83 | }
84 |
85 | createContext (languageCode, templateData) {
86 | return new I18nContext(this.repository, this.config, languageCode, templateData)
87 | }
88 |
89 | middleware () {
90 | return (ctx, next) => {
91 | const session = this.config.useSession && ctx[this.config.sessionName]
92 | const languageCode = (session && session.__language_code) || (ctx.from && ctx.from.language_code)
93 |
94 | ctx.i18n = new I18nContext(
95 | this.repository,
96 | this.config,
97 | languageCode,
98 | {
99 | from: ctx.from,
100 | chat: ctx.chat
101 | }
102 | )
103 |
104 | return next().then(() => {
105 | if (session) {
106 | session.__language_code = ctx.i18n.locale()
107 | }
108 | })
109 | }
110 | }
111 |
112 | t (languageCode, resourceKey, templateData) {
113 | const context = new I18nContext(this.repository, this.config, languageCode, templateData)
114 | return context.t(resourceKey)
115 | }
116 | }
117 |
118 | function compileTemplates (root) {
119 | Object.keys(root).forEach((key) => {
120 | if (!root[key]) {
121 | return
122 | }
123 | if (Array.isArray(root[key])) {
124 | root[key] = root[key].map(compileTemplates)
125 | }
126 | if (typeof root[key] === 'object') {
127 | root[key] = compileTemplates(root[key])
128 | }
129 | if (typeof root[key] === 'string') {
130 | if (root[key].includes('${')) {
131 | root[key] = compile(root[key])
132 | } else {
133 | const value = root[key]
134 | root[key] = () => value
135 | }
136 | }
137 | })
138 | return root
139 | }
140 |
141 | function getTemplateKeysRecursive (root, prefix = '') {
142 | let keys = []
143 | for (const key of Object.keys(root)) {
144 | const subKey = prefix ? prefix + '.' + key : key
145 | if (typeof root[key] === 'object') {
146 | keys = keys.concat(getTemplateKeysRecursive(root[key], subKey))
147 | } else {
148 | keys.push(subKey)
149 | }
150 | }
151 |
152 | return keys
153 | }
154 |
155 | I18n.match = function (resourceKey, templateData) {
156 | return (text, ctx) => (text && ctx && ctx.i18n && text === ctx.i18n.t(resourceKey, templateData)) ? [text] : null
157 | }
158 |
159 | I18n.reply = function (resourceKey, extra) {
160 | return ({ reply, i18n }) => reply(i18n.t(resourceKey), extra)
161 | }
162 |
163 | I18n.pluralize = pluralize
164 | module.exports = I18n
165 |
--------------------------------------------------------------------------------
/lib/index.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'telegraf-i18n' {
2 | interface Config {
3 | directory?: string;
4 | useSession?: boolean;
5 | sessionName?: string;
6 | allowMissing?: boolean;
7 | defaultLanguageOnMissing?: boolean;
8 | defaultLanguage?: string;
9 | }
10 |
11 | type ContextUpdate = (ctx: any, next?: (() => any) | undefined) => any;
12 |
13 | export class I18n {
14 | constructor (input: Config);
15 | loadLocales (directory: string): void;
16 | loadLocale (languageCode: string, i18Data: object): void;
17 | resetLocale (languageCode: string): void;
18 | availableLocales (): string[];
19 | resourceKeys (languageCode: string): string[];
20 | missingKeys (languageOfInterest: string, referenceLanguage?: string): string[];
21 | overspecifiedKeys (languageOfInterest: string, referenceLanguage?: string): string[];
22 | translationProgress (languageOfInterest: string, referenceLanguage?: string): number;
23 | middleware(): ContextUpdate;
24 | createContext (languageCode: string, templateData: object): void;
25 | t (languageCode?: string, resourceKey?: string, templateData?: object): string;
26 | t (resourceKey?: string, templateData?: object): string;
27 | locale (): string;
28 | locale (languageCode?: string): void;
29 | }
30 |
31 | export default I18n;
32 | }
33 |
--------------------------------------------------------------------------------
/lib/pluralize.js:
--------------------------------------------------------------------------------
1 | // https://developer.mozilla.org/en-US/docs/Mozilla/Localization/Localization_and_Plurals
2 |
3 | const pluralRules = {
4 | english: (n) => n !== 1 ? 1 : 0,
5 | french: (n) => n > 1 ? 1 : 0,
6 | russian: (n) => {
7 | if (n % 10 === 1 && n % 100 !== 11) {
8 | return 0
9 | }
10 | return n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2
11 | },
12 | czech: (n) => {
13 | if (n === 1) {
14 | return 0
15 | }
16 | return (n >= 2 && n <= 4) ? 1 : 2
17 | },
18 | polish: (n) => {
19 | if (n === 1) {
20 | return 0
21 | }
22 | return n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2
23 | },
24 | icelandic: (n) => (n % 10 !== 1 || n % 100 === 11) ? 1 : 0,
25 | chinese: () => 0,
26 | arabic: (n) => {
27 | if (n >= 0 && n < 3) {
28 | return n
29 | }
30 | if (n % 100 <= 10) {
31 | return 3
32 | }
33 | if (n >= 11 && n % 100 <= 99) {
34 | return 4
35 | }
36 | return 5
37 | }
38 | }
39 |
40 | const mapping = {
41 | english: ['da', 'de', 'en', 'es', 'fi', 'el', 'he', 'hu', 'it', 'nl', 'no', 'pt', 'sv', 'br'],
42 | chinese: ['fa', 'id', 'ja', 'ko', 'lo', 'ms', 'th', 'tr', 'zh', 'jp'],
43 | french: ['fr', 'tl', 'pt-br'],
44 | russian: ['hr', 'ru', 'uk', 'uz'],
45 | czech: ['cs', 'sk'],
46 | icelandic: ['is'],
47 | polish: ['pl'],
48 | arabic: ['ar']
49 | }
50 |
51 | module.exports = function pluralize (number, ...forms) {
52 | const code = this.shortLanguageCode
53 | let key = Object.keys(mapping).find((key) => mapping[key].includes(code))
54 | if (!key) {
55 | console.warn(`i18n::Pluralize: Unsupported language ${code}`)
56 | key = 'english'
57 | }
58 | const form = forms[pluralRules[key](number)]
59 | return typeof form === 'function' ? form(number) : `${number} ${form}`
60 | }
61 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "telegraf-i18n",
3 | "version": "6.6.0",
4 | "description": "Telegraf i18n engine",
5 | "main": "lib/i18n.js",
6 | "types": "lib/index.d.ts",
7 | "repository": {
8 | "type": "git",
9 | "url": "git+ssh://git@github.com/telegraf/telegraf-i18n.git"
10 | },
11 | "keywords": [
12 | "telegram bot",
13 | "telegraf",
14 | "bot framework",
15 | "i18n",
16 | "internationalization",
17 | "middleware"
18 | ],
19 | "author": "Vitaly Domnikov ",
20 | "license": "MIT",
21 | "bugs": {
22 | "url": "https://github.com/telegraf/telegraf-i18n/issues"
23 | },
24 | "homepage": "https://github.com/telegraf/telegraf-i18n#readme",
25 | "engines": {
26 | "node": ">=8"
27 | },
28 | "files": [
29 | "lib"
30 | ],
31 | "scripts": {
32 | "test": "eslint . && ava"
33 | },
34 | "dependencies": {
35 | "compile-template": "^0.3.1",
36 | "debug": "^4.0.1",
37 | "js-yaml": "^3.6.1"
38 | },
39 | "peerDependencies": {
40 | "telegraf": "^3.7.1"
41 | },
42 | "devDependencies": {
43 | "ava": "^2.2.0",
44 | "eslint": "^6.1.0",
45 | "eslint-config-standard": "^13.0.1",
46 | "eslint-plugin-ava": "^7.1.0",
47 | "eslint-plugin-import": "^2.2.0",
48 | "eslint-plugin-node": "^9.1.0",
49 | "eslint-plugin-promise": "^4.0.0",
50 | "eslint-plugin-standard": "^4.0.0",
51 | "telegraf": "^3.7.1"
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/telegraf/telegraf-i18n)
2 | [](https://www.npmjs.com/package/telegraf-i18n)
3 | [](http://standardjs.com/)
4 |
5 | # i18n for Telegraf
6 |
7 | Internationalization middleware for [Telegraf](https://github.com/telegraf/telegraf).
8 |
9 | ## Installation
10 |
11 | ```js
12 | $ npm install telegraf-i18n
13 | ```
14 |
15 | ## Example
16 |
17 | ```js
18 | const Telegraf = require('telegraf')
19 | const TelegrafI18n = require('telegraf-i18n')
20 |
21 | /*
22 | yaml and json are ok
23 | Example directory structure:
24 | ├── locales
25 | │ ├── en.yaml
26 | │ ├── en-US.yaml
27 | │ ├── it.json
28 | │ └── ru.yaml
29 | └── bot.js
30 | */
31 |
32 | const i18n = new TelegrafI18n({
33 | defaultLanguage: 'en',
34 | allowMissing: false, // Default true
35 | directory: path.resolve(__dirname, 'locales')
36 | })
37 |
38 | // Also you can provide i18n data directly
39 | i18n.loadLocale('en', {greeting: 'Hello!'})
40 |
41 | const app = new Telegraf(process.env.BOT_TOKEN)
42 |
43 | // telegraf-i18n can save current locale setting into session.
44 | const i18n = new TelegrafI18n({
45 | useSession: true,
46 | defaultLanguageOnMissing: true, // implies allowMissing = true
47 | directory: path.resolve(__dirname, 'locales')
48 | })
49 |
50 | app.use(Telegraf.memorySession())
51 | app.use(i18n.middleware())
52 |
53 | app.hears('/start', (ctx) => {
54 | const message = ctx.i18n.t('greeting', {
55 | username: ctx.from.username
56 | })
57 | return ctx.reply(message)
58 | })
59 |
60 | app.startPolling()
61 | ```
62 |
63 | See full [example](/examples).
64 |
65 | ## User context
66 |
67 | Telegraf user context props and functions:
68 |
69 | ```js
70 | app.use((ctx) => {
71 | ctx.i18n.locale() // Get current locale
72 | ctx.i18n.locale(code) // Set current locale
73 | ctx.i18n.t(resourceKey, [context]) // Get resource value (context will be used by template engine)
74 | });
75 | ```
76 |
77 | ## Helpers
78 |
79 | ```js
80 | const { match, reply } = require('telegraf-i18n')
81 |
82 | // In case you use custom keyboard with localized labels.
83 | bot.hears(match('keyboard.foo'), (ctx) => ...)
84 |
85 | //Reply helper
86 | bot.command('help', reply('help'))
87 | ```
88 |
--------------------------------------------------------------------------------
/test/analyse-language-repository.js:
--------------------------------------------------------------------------------
1 | const test = require('ava')
2 |
3 | const I18n = require('../lib/i18n.js')
4 |
5 | test('resourceKeys flat', t => {
6 | const i18n = new I18n()
7 | i18n.loadLocale('en', {
8 | greeting: 'Hello!'
9 | })
10 |
11 | t.deepEqual(i18n.resourceKeys('en'), [
12 | 'greeting'
13 | ])
14 | })
15 |
16 | test('resourceKeys with depth', t => {
17 | const i18n = new I18n()
18 | i18n.loadLocale('en', {
19 | greeting: 'Hello!',
20 | foo: {
21 | bar: '42',
22 | hell: {
23 | devil: 666
24 | }
25 | }
26 | })
27 |
28 | t.deepEqual(i18n.resourceKeys('en'), [
29 | 'greeting',
30 | 'foo.bar',
31 | 'foo.hell.devil'
32 | ])
33 | })
34 |
35 | test('resourceKeys of not existing locale are empty', t => {
36 | const i18n = new I18n()
37 | i18n.loadLocale('en', {
38 | greeting: 'Hello!'
39 | })
40 |
41 | t.deepEqual(i18n.resourceKeys('de'), [])
42 | })
43 |
44 | function createMultiLanguageExample () {
45 | const i18n = new I18n()
46 | i18n.loadLocale('en', {
47 | greeting: 'Hello!',
48 | checkout: 'Thank you!'
49 | })
50 | i18n.loadLocale('ru', {
51 | greeting: 'Привет!'
52 | })
53 | return i18n
54 | }
55 |
56 | test('availableLocales', t => {
57 | const i18n = createMultiLanguageExample()
58 | t.deepEqual(i18n.availableLocales(), [
59 | 'en',
60 | 'ru'
61 | ])
62 | })
63 |
64 | test('missingKeys ', t => {
65 | const i18n = createMultiLanguageExample()
66 | t.deepEqual(i18n.missingKeys('en', 'ru'), [])
67 | t.deepEqual(i18n.missingKeys('ru'), [
68 | 'checkout'
69 | ])
70 | })
71 |
72 | test('overspecifiedKeys', t => {
73 | const i18n = createMultiLanguageExample()
74 | t.deepEqual(i18n.overspecifiedKeys('ru'), [])
75 | t.deepEqual(i18n.overspecifiedKeys('en', 'ru'), [
76 | 'checkout'
77 | ])
78 | })
79 |
80 | test('translationProgress', t => {
81 | const i18n = createMultiLanguageExample()
82 |
83 | // 'checkout' is missing
84 | t.is(i18n.translationProgress('ru'), 0.5)
85 |
86 | // Overspecified (unneeded 'checkout') but everything required is there
87 | t.deepEqual(i18n.translationProgress('en', 'ru'), 1)
88 | })
89 |
--------------------------------------------------------------------------------
/test/basics.js:
--------------------------------------------------------------------------------
1 | const test = require('ava')
2 |
3 | const I18n = require('../lib/i18n.js')
4 |
5 | test('can translate', t => {
6 | const i18n = new I18n()
7 | i18n.loadLocale('en', {
8 | greeting: 'Hello!'
9 | })
10 | t.is(i18n.t('en', 'greeting'), 'Hello!')
11 | })
12 |
13 | test('allowMissing false throws', t => {
14 | const i18n = new I18n({
15 | allowMissing: false
16 | })
17 | t.throws(() => {
18 | i18n.t('en', 'greeting')
19 | }, 'telegraf-i18n: \'en.greeting\' not found')
20 | })
21 |
--------------------------------------------------------------------------------
/test/pluralize.js:
--------------------------------------------------------------------------------
1 | const test = require('ava')
2 |
3 | const I18n = require('../lib/i18n.js')
4 |
5 | test('can pluralize', t => {
6 | const i18n = new I18n()
7 | i18n.loadLocale('en', {
8 | // eslint-disable-next-line no-template-curly-in-string
9 | pluralize: "${pluralize(n, 'There was an apple', 'There were apples')}"
10 | })
11 | t.is(i18n.t('en', 'pluralize', { n: 0 }), '0 There were apples')
12 | t.is(i18n.t('en', 'pluralize', { n: 1 }), '1 There was an apple')
13 | t.is(i18n.t('en', 'pluralize', { n: 5 }), '5 There were apples')
14 | })
15 |
16 | test('can pluralize using functional forms', t => {
17 | const i18n = new I18n()
18 | i18n.loadLocale('en', {
19 | // eslint-disable-next-line no-template-curly-in-string
20 | pluralize: "${pluralize(n, n => 'There was an apple', n => 'There were ' + n + ' apples')}"
21 | })
22 | t.is(i18n.t('en', 'pluralize', { n: 0 }), 'There were 0 apples')
23 | t.is(i18n.t('en', 'pluralize', { n: 1 }), 'There was an apple')
24 | t.is(i18n.t('en', 'pluralize', { n: 5 }), 'There were 5 apples')
25 | })
26 |
--------------------------------------------------------------------------------