Actualización

This commit is contained in:
Xes
2025-04-10 12:53:50 +02:00
parent f7a0ba2b2f
commit 2001ceddea
39284 changed files with 991962 additions and 0 deletions

View File

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

View File

@@ -0,0 +1,14 @@
{
"name": "i18next",
"homepage": "https://github.com/i18next/i18next",
"version": "19.5.1",
"_release": "19.5.1",
"_resolution": {
"type": "version",
"tag": "v19.5.1",
"commit": "bc31ddd478eba54d7bbf3b52997ec4379498611a"
},
"_source": "https://github.com/i18next/i18next.git",
"_target": ">=2.4.0",
"_originalSource": "i18next"
}

View File

@@ -0,0 +1,40 @@
engines:
duplication:
enabled: true
config:
languages:
- ruby
- javascript:
mass_threshold: 58
- python
- php
eslint:
enabled: true
fixme:
enabled: true
ratings:
paths:
- 'src/**/*'
exclude_paths:
- test/
- coverage/
- dist/
- 'i18next.js'
- 'i18next.min.js'
- 'index.d.ts'
- 'rollup.config.js'
- 'karma.conf.js'
- 'karma.backward.conf.js'
checks:
file-lines:
config:
threshold: 300
method-lines:
config:
threshold: 70
complex-logic:
config:
threshold: 5
nested-control-flow:
config:
threshold: 5

View File

@@ -0,0 +1 @@
repo_token: SHBhCq4BLOpxJww3DG6g7dtKcuhG62zcK

View File

@@ -0,0 +1,9 @@
# EditorConfig is awesome: http://EditorConfig.org
root = true
[*.{js,jsx,json}]
end_of_line = lf
insert_final_newline = true
charset = utf-8
indent_style = space
indent_size = 2

View File

@@ -0,0 +1,3 @@
**/dist/*
**/node_modules/*
**/*.min.*

View File

@@ -0,0 +1,30 @@
parser: babel-eslint
extends:
- airbnb
- prettier
rules:
max-len: [0, 100]
no-constant-condition: 0
arrow-body-style: [1, "as-needed"]
comma-dangle: [2, "never"]
padded-blocks: [0, "never"]
no-unused-vars: [2, {vars: all, args: none}]
prefer-arrow-callback: 1
no-plusplus: 0
no-param-reassign: 0
no-unused-expressions: 1
react/prop-types:
- 0
- ignore: #coming from hoc
- location
- fields
- handleSubmit
globals:
expect: true
describe: true
it: true
before: true
after: true
window: true

View File

@@ -0,0 +1,23 @@
# Ignore specific files
.settings.xml
.monitor
.DS_Store
*.orig
npm-debug.log
npm-debug.log.*
*.dat
.vscode
# Ignore various temporary files
*~
*.swp
# Ignore various Node.js related directories and files
node_modules
node_modules/**/*
coverage/**/*
dist/**/*
# Ignore IntelliJ based IDEs project files
.idea

View File

@@ -0,0 +1,24 @@
src/
test/
coverage/
assets/
.circleci/
.babelrc
.editorconfig
.eslintignore
.eslintrc
.gitignore
bower.json
gulpfile.js
karma.conf.js
karma.backward.conf.js
sample.html
.codeclimate.yml
.coveralls.yml
tsconfig.json
.prettierignore
.prettierrc
rollup.config.js
tsconfig.nonEsModuleInterop
tslint.json
CHANGELOG.md

View File

@@ -0,0 +1,3 @@
i18next.js
i18next.min.js
src/PluralResolver.js

View File

@@ -0,0 +1,8 @@
{
"bracketSpacing": true,
"jsxBracketSameLine": false,
"printWidth": 100,
"semi": true,
"singleQuote": true,
"trailingComma": "all"
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2017 i18next
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,54 @@
# i18next: learn once - translate everywhere [![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Awesome%20i18next:%20learn%20once%20-%20translate%20everywhere%20-%20the%20internationalization%20ecosystem%20&url=https://github.com/i18next/i18next&via=jamuhl&hashtags=i18n,javascript,dev)
[![CircleCI](https://circleci.com/gh/i18next/i18next.svg?style=svg)](https://circleci.com/gh/i18next/i18next)
[![Code Climate](https://codeclimate.com/github/codeclimate/codeclimate/badges/gpa.svg)](https://codeclimate.com/github/i18next/i18next)
[![Coveralls](https://img.shields.io/coveralls/i18next/i18next/master.svg?style=flat-square)](https://coveralls.io/github/i18next/i18next)
[![Package Quality](http://npm.packagequality.com/shield/i18next.svg)](http://packagequality.com/#?package=i18next)
[![cdnjs version](https://img.shields.io/cdnjs/v/i18next.svg?style=flat-square)](https://cdnjs.com/libraries/i18next)
[![npm version](https://img.shields.io/npm/v/i18next.svg?style=flat-square)](https://www.npmjs.com/package/i18next)
[![David](https://img.shields.io/david/i18next/i18next.svg?style=flat-square)](https://david-dm.org/i18next/i18next)
i18next is a very popular internationalization framework for browser or any other javascript environment (eg. node.js).
![ecosystem](https://raw.githubusercontent.com/i18next/i18next/master/assets/i18next-ecosystem.jpg)
i18next provides:
- Flexible connection to [backend](https://www.i18next.com/plugins-and-utils.html#backends) (loading translations via xhr, ...)
- Optional [caching](https://www.i18next.com/plugins-and-utils.html#caches), user [language detection](https://www.i18next.com/plugins-and-utils.html#language-detector), ...
- Proper [pluralizations](https://www.i18next.com/plurals.html)
- Translation [context](https://www.i18next.com/context.html)
- [Nesting](https://www.i18next.com/nesting.html), [Variable replacement](https://www.i18next.com/interpolation.html)
- Flexibility: [Use it everywhere](https://www.i18next.com/supported-frameworks.html)
- Extensibility: eg. [sprintf](https://www.i18next.com/plugins-and-utils.html#post-processors)
- ...
For more information visit the website:
- [Getting started](https://www.i18next.com/getting-started.html)
- [Translation Functionality](https://www.i18next.com/essentials.html)
- [API](https://www.i18next.com/api.html)
Our focus is providing the core to building a booming ecosystem. Independent of the building blocks you choose, be it react, angular or even good old jquery proper translation capabilities are just [one step away](https://www.i18next.com/supported-frameworks.html).
---
<h3 align="center">Gold Sponsors</h3>
<p align="center">
<a href="https://locize.com/" target="_blank">
<img src="https://raw.githubusercontent.com/i18next/i18next/master/assets/locize_sponsor_240.gif" width="240px">
</a>
</p>
---
**From the creators of i18next: localization as a service - locize.com**
A translation management system built around the i18next ecosystem - [locize.com](https://locize.com).
![locize](https://locize.com/img/ads/github_locize.png)
With using [locize](http://locize.com/?utm_source=i18next_readme&utm_medium=github) you directly support the future of i18next.
---

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,5 @@
/* eslint no-var: 0 */
var main = require('./dist/cjs/i18next.js');
module.exports = main;
module.exports.default = main;

View File

@@ -0,0 +1,50 @@
module.exports = function(karma) {
karma.set({
frameworks: ['mocha', 'expect', 'sinon', 'browserify'],
files: [
//'vendor/external.js',
'test/backward/**/*.compat.js',
{ pattern: 'test/backward/locales/**/*.json', watched: true, included: false, served: true },
],
proxies: {
'/locales': 'http://localhost:9877/base/test/backward/locales',
},
reporters: ['spec'],
preprocessors: {
'test/backward/**/*.compat.js': ['browserify'],
'test/backward/compatibility/**/*.js': ['browserify'],
},
browsers: ['HeadlessChrome'],
customLaunchers: {
HeadlessChrome: {
base: 'ChromeHeadless',
flags: ['—no-sandbox'],
},
},
port: 9877,
//logLevel: 'LOG_DEBUG',
//singleRun: true,
//autoWatch: false,
//
// client: {
// mocha: {
// reporter: 'spec', // change Karma's debug.html to the mocha web reporter
// ui: 'tdd'
// }
// },
// browserify configuration
browserify: {
debug: true,
transform: [['babelify', { presets: ['@babel/preset-env'] }] /*, 'brfs' */],
},
});
};

View File

@@ -0,0 +1,60 @@
//const istanbul = require( 'browserify-istanbul' );
module.exports = function(karma) {
karma.set({
frameworks: ['mocha', 'chai', 'sinon', 'browserify'],
files: [
//'vendor/external.js',
'test/**/*.spec.js',
{ pattern: 'test/locales/**/*.json', watched: true, included: false, served: true },
],
proxies: {
'/locales': 'http://localhost:9876/base/test/locales',
},
reporters: ['coverage', 'coveralls', 'spec'],
preprocessors: {
'test/**/*.spec.js': ['browserify'],
'src/**/*.js': ['browserify', 'coverage'],
},
browsers: ['HeadlessChrome'],
customLaunchers: {
HeadlessChrome: {
base: 'ChromeHeadless',
flags: ['—no-sandbox'],
},
},
port: 9876,
//logLevel: 'LOG_DEBUG',
//singleRun: true,
//autoWatch: false,
//
// client: {
// mocha: {
// reporter: 'spec', // change Karma's debug.html to the mocha web reporter
// ui: 'tdd'
// }
// },
// browserify configuration
browserify: {
debug: true,
transform: [
['babelify', { presets: ['@babel/preset-env'] }],
/*'brfs',*/ 'browserify-istanbul',
],
},
coverageReporter: {
type: 'lcov', //'html', // disabled - erroring now, https://github.com/karma-runner/karma-coverage/issues/157
dir: 'coverage/',
},
});
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,116 @@
{
"name": "i18next",
"version": "19.5.1",
"description": "i18next internationalization framework",
"main": "./dist/cjs/i18next.js",
"module": "./dist/esm/i18next.js",
"types": "./index.d.ts",
"keywords": [
"i18next",
"internationalization",
"i18n",
"translation",
"localization",
"l10n",
"globalization",
"gettext"
],
"homepage": "http://i18next.com",
"bugs": "https://github.com/i18next/i18next/issues",
"repository": {
"type": "git",
"url": "https://github.com/i18next/i18next.git"
},
"dependencies": {
"@babel/runtime": "^7.10.1"
},
"devDependencies": {
"@babel/core": "^7.10.1",
"@babel/plugin-proposal-async-generator-functions": "^7.2.0",
"@babel/plugin-proposal-object-rest-spread": "^7.3.2",
"@babel/plugin-transform-modules-commonjs": "^7.2.0",
"@babel/plugin-transform-runtime": "^7.2.0",
"@babel/polyfill": "^7.2.5",
"@babel/preset-env": "^7.3.1",
"@babel/preset-react": "^7.0.0",
"@babel/register": "^7.0.0",
"babel-eslint": "^10.0.1",
"babelify": "^10.0.0",
"browserify": "14.0.0",
"browserify-istanbul": "2.0.0",
"chai": "3.5.0",
"coveralls": "2.11.16",
"cpy-cli": "^2.0.0",
"dtslint": "^0.4.2",
"eslint": "3.15.0",
"eslint-config-airbnb": "14.1.0",
"eslint-config-prettier": "^3.6.0",
"eslint-plugin-import": "2.2.0",
"eslint-plugin-jsx-a11y": "4.0.0",
"eslint-plugin-react": "6.9.0",
"husky": "^1.3.1",
"i18next-browser-languagedetector": "1.0.1",
"i18next-localstorage-cache": "0.3.0",
"i18next-sprintf-postprocessor": "0.2.2",
"i18next-xhr-backend": "1.3.0",
"istanbul": "gotwarlost/istanbul#source-map",
"karma": "2.0.0",
"karma-browserify": "5.1.1",
"karma-chai": "0.1.0",
"karma-chrome-launcher": "2.2.0",
"karma-cli": "1.0.1",
"karma-coverage": "douglasduteil/karma-coverage#next",
"karma-coveralls": "1.1.2",
"karma-expect": "1.1.3",
"karma-mocha": "1.3.0",
"karma-rollup-preprocessor": "3.0.3",
"karma-sinon": "1.0.5",
"karma-spec-reporter": "0.0.26",
"lint-staged": "^8.1.0",
"mocha": "3.2.0",
"prettier": "^1.15.3",
"rimraf": "2.5.4",
"rollup": "1.1.2",
"rollup-plugin-babel": "^4.3.2",
"rollup-plugin-commonjs": "^9.2.0",
"rollup-plugin-node-resolve": "4.0.0",
"rollup-plugin-terser": "^4.0.4",
"sinon": "1.17.7",
"tslint": "^5.12.1",
"typescript": "^3.6.4",
"watchify": "3.9.0"
},
"scripts": {
"pretest": "npm run test:typescript && npm run test:typescript:noninterop",
"test": "npm run test:new && npm run test:compat",
"test:new": "karma start karma.conf.js --singleRun",
"test:compat": "karma start karma.backward.conf.js --singleRun",
"test:typescript": "tslint --project tsconfig.json",
"test:typescript:noninterop": "tslint --project tsconfig.nonEsModuleInterop.json",
"tdd": "karma start karma.conf.js",
"tdd:compat": "karma start karma.backward.conf.js",
"build": "rimraf dist && rollup -c && cpy \"./dist/umd/*.js\" ./",
"preversion": "npm run test && npm run build && git push",
"postversion": "git push && git push --tags",
"prettier": "prettier --write \"{,**/}*.{ts,tsx,js,json,md}\""
},
"author": "Jan Mühlemann <jan.muehlemann@gmail.com> (https://github.com/jamuhl)",
"license": "MIT",
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"linters": {
"*.{ts,tsx,js,json,md}": [
"prettier --write",
"git add"
]
},
"ignore": [
"**/i18next.js",
"**/i18next.min.js"
]
}
}

View File

@@ -0,0 +1,43 @@
import babel from 'rollup-plugin-babel';
import nodeResolve from 'rollup-plugin-node-resolve';
import { terser } from 'rollup-plugin-terser';
import pkg from './package.json';
const getBabelOptions = ({ useESModules }) => ({
exclude: /node_modules/,
runtimeHelpers: true,
plugins: [['@babel/transform-runtime', { useESModules }]],
comments: false,
});
const input = './src/index.js';
const name = 'i18next';
// check relative and absolute paths for windows and unix
const external = id => !id.startsWith('.') && !id.startsWith('/') && !id.includes(':');
export default [
{
input,
output: { format: 'cjs', file: pkg.main },
external,
plugins: [babel(getBabelOptions({ useESModules: false }))],
},
{
input,
output: { format: 'esm', file: pkg.module },
external,
plugins: [babel(getBabelOptions({ useESModules: true }))],
},
{
input,
output: { format: 'umd', name, file: `dist/umd/${name}.js` },
plugins: [babel(getBabelOptions({ useESModules: true })), nodeResolve()],
},
{
input,
output: { format: 'umd', name, file: `dist/umd/${name}.min.js` },
plugins: [babel(getBabelOptions({ useESModules: true })), nodeResolve(), terser()],
},
];

View File

@@ -0,0 +1,44 @@
<html>
<head>
<script src="./i18next.min.js" ></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/i18next-browser-languagedetector/1.0.1/i18nextBrowserLanguageDetector.min.js" ></script>
</head>
<body>
<div>open console</div>
<script>
// use plugins and options as needed, for options, detail see
// http://i18next.com/docs/
i18next
.use(window.i18nextBrowserLanguageDetector)
.init({
//lng: 'en', // evtl. use language-detector https://github.com/i18next/i18next-browser-languageDetector
resources: { // evtl. load via xhr https://github.com/i18next/i18next-xhr-backend
en: {
translation: {
input: {
placeholder: "a placeholder"
},
nav: {
home: 'Home',
page1: 'Page One',
page2: 'Page Two'
}
}
}
},
debug: true
}, function(err, t) {
var fixedT = i18next.getFixedT(null, null);
console.warn(fixedT('nav.page3', 'support'), fixedT('nav.page3', 'support') === 'support');
console.warn(i18next.t('input.placeholder'), i18next.t('input.placeholder') === 'a placeholder');
console.warn(i18next.t('nav.home'), i18next.t('nav.home') === 'Home');
console.warn(i18next);
});
</script>
</body>
</html>

View File

@@ -0,0 +1,224 @@
import * as utils from './utils.js';
import baseLogger from './logger.js';
import EventEmitter from './EventEmitter.js';
function remove(arr, what) {
let found = arr.indexOf(what);
while (found !== -1) {
arr.splice(found, 1);
found = arr.indexOf(what);
}
}
class Connector extends EventEmitter {
constructor(backend, store, services, options = {}) {
super();
if (utils.isIE10) {
EventEmitter.call(this); // <=IE10 fix (unable to call parent constructor)
}
this.backend = backend;
this.store = store;
this.services = services;
this.languageUtils = services.languageUtils;
this.options = options;
this.logger = baseLogger.create('backendConnector');
this.state = {};
this.queue = [];
if (this.backend && this.backend.init) {
this.backend.init(services, options.backend, options);
}
}
queueLoad(languages, namespaces, options, callback) {
// find what needs to be loaded
const toLoad = [];
const pending = [];
const toLoadLanguages = [];
const toLoadNamespaces = [];
languages.forEach(lng => {
let hasAllNamespaces = true;
namespaces.forEach(ns => {
const name = `${lng}|${ns}`;
if (!options.reload && this.store.hasResourceBundle(lng, ns)) {
this.state[name] = 2; // loaded
} else if (this.state[name] < 0) {
// nothing to do for err
} else if (this.state[name] === 1) {
if (pending.indexOf(name) < 0) pending.push(name);
} else {
this.state[name] = 1; // pending
hasAllNamespaces = false;
if (pending.indexOf(name) < 0) pending.push(name);
if (toLoad.indexOf(name) < 0) toLoad.push(name);
if (toLoadNamespaces.indexOf(ns) < 0) toLoadNamespaces.push(ns);
}
});
if (!hasAllNamespaces) toLoadLanguages.push(lng);
});
if (toLoad.length || pending.length) {
this.queue.push({
pending,
loaded: {},
errors: [],
callback,
});
}
return {
toLoad,
pending,
toLoadLanguages,
toLoadNamespaces,
};
}
loaded(name, err, data) {
const s = name.split('|');
const lng = s[0];
const ns = s[1];
if (err) this.emit('failedLoading', lng, ns, err);
if (data) {
this.store.addResourceBundle(lng, ns, data);
}
// set loaded
this.state[name] = err ? -1 : 2;
// consolidated loading done in this run - only emit once for a loaded namespace
const loaded = {};
// callback if ready
this.queue.forEach(q => {
utils.pushPath(q.loaded, [lng], ns);
remove(q.pending, name);
if (err) q.errors.push(err);
if (q.pending.length === 0 && !q.done) {
// only do once per loaded -> this.emit('loaded', q.loaded);
Object.keys(q.loaded).forEach(l => {
if (!loaded[l]) loaded[l] = [];
if (q.loaded[l].length) {
q.loaded[l].forEach(ns => {
if (loaded[l].indexOf(ns) < 0) loaded[l].push(ns);
});
}
});
/* eslint no-param-reassign: 0 */
q.done = true;
if (q.errors.length) {
q.callback(q.errors);
} else {
q.callback();
}
}
});
// emit consolidated loaded event
this.emit('loaded', loaded);
// remove done load requests
this.queue = this.queue.filter(q => !q.done);
}
read(lng, ns, fcName, tried = 0, wait = 350, callback) {
if (!lng.length) return callback(null, {}); // noting to load
return this.backend[fcName](lng, ns, (err, data) => {
if (err && data /* = retryFlag */ && tried < 5) {
setTimeout(() => {
this.read.call(this, lng, ns, fcName, tried + 1, wait * 2, callback);
}, wait);
return;
}
callback(err, data);
});
}
/* eslint consistent-return: 0 */
prepareLoading(languages, namespaces, options = {}, callback) {
if (!this.backend) {
this.logger.warn('No backend was added via i18next.use. Will not load resources.');
return callback && callback();
}
if (typeof languages === 'string') languages = this.languageUtils.toResolveHierarchy(languages);
if (typeof namespaces === 'string') namespaces = [namespaces];
const toLoad = this.queueLoad(languages, namespaces, options, callback);
if (!toLoad.toLoad.length) {
if (!toLoad.pending.length) callback(); // nothing to load and no pendings...callback now
return null; // pendings will trigger callback
}
toLoad.toLoad.forEach(name => {
this.loadOne(name);
});
}
load(languages, namespaces, callback) {
this.prepareLoading(languages, namespaces, {}, callback);
}
reload(languages, namespaces, callback) {
this.prepareLoading(languages, namespaces, { reload: true }, callback);
}
loadOne(name, prefix = '') {
const s = name.split('|');
const lng = s[0];
const ns = s[1];
this.read(lng, ns, 'read', undefined, undefined, (err, data) => {
if (err) this.logger.warn(`${prefix}loading namespace ${ns} for language ${lng} failed`, err);
if (!err && data)
this.logger.log(`${prefix}loaded namespace ${ns} for language ${lng}`, data);
this.loaded(name, err, data);
});
}
saveMissing(languages, namespace, key, fallbackValue, isUpdate, options = {}) {
if (
this.services.utils &&
this.services.utils.hasLoadedNamespace &&
!this.services.utils.hasLoadedNamespace(namespace)
) {
this.logger.warn(
`did not save key "${key}" as the namespace "${namespace}" was not yet loaded`,
'This means something IS WRONG in your setup. You access the t function before i18next.init / i18next.loadNamespace / i18next.changeLanguage was done. Wait for the callback or Promise to resolve before accessing it!!!',
);
return;
}
// ignore non valid keys
if (key === undefined || key === null || key === '') return;
if (this.backend && this.backend.create) {
this.backend.create(languages, namespace, key, fallbackValue, null /* unused callback */, {
...options,
isUpdate,
});
}
// write to store to avoid resending
if (!languages || !languages[0]) return;
this.store.addResource(languages[0], namespace, key, fallbackValue);
}
}
export default Connector;

View File

@@ -0,0 +1,41 @@
class EventEmitter {
constructor() {
this.observers = {};
}
on(events, listener) {
events.split(' ').forEach(event => {
this.observers[event] = this.observers[event] || [];
this.observers[event].push(listener);
});
return this;
}
off(event, listener) {
if (!this.observers[event]) return;
if (!listener) {
delete this.observers[event];
return;
}
this.observers[event] = this.observers[event].filter(l => l !== listener);
}
emit(event, ...args) {
if (this.observers[event]) {
const cloned = [].concat(this.observers[event]);
cloned.forEach(observer => {
observer(...args);
});
}
if (this.observers['*']) {
const cloned = [].concat(this.observers['*']);
cloned.forEach(observer => {
observer.apply(observer, [event, ...args]);
});
}
}
}
export default EventEmitter;

View File

@@ -0,0 +1,232 @@
import * as utils from './utils.js';
import baseLogger from './logger.js';
class Interpolator {
constructor(options = {}) {
this.logger = baseLogger.create('interpolator');
this.options = options;
this.format = (options.interpolation && options.interpolation.format) || (value => value);
this.init(options);
}
/* eslint no-param-reassign: 0 */
init(options = {}) {
if (!options.interpolation) options.interpolation = { escapeValue: true };
const iOpts = options.interpolation;
this.escape = iOpts.escape !== undefined ? iOpts.escape : utils.escape;
this.escapeValue = iOpts.escapeValue !== undefined ? iOpts.escapeValue : true;
this.useRawValueToEscape =
iOpts.useRawValueToEscape !== undefined ? iOpts.useRawValueToEscape : false;
this.prefix = iOpts.prefix ? utils.regexEscape(iOpts.prefix) : iOpts.prefixEscaped || '{{';
this.suffix = iOpts.suffix ? utils.regexEscape(iOpts.suffix) : iOpts.suffixEscaped || '}}';
this.formatSeparator = iOpts.formatSeparator
? iOpts.formatSeparator
: iOpts.formatSeparator || ',';
this.unescapePrefix = iOpts.unescapeSuffix ? '' : iOpts.unescapePrefix || '-';
this.unescapeSuffix = this.unescapePrefix ? '' : iOpts.unescapeSuffix || '';
this.nestingPrefix = iOpts.nestingPrefix
? utils.regexEscape(iOpts.nestingPrefix)
: iOpts.nestingPrefixEscaped || utils.regexEscape('$t(');
this.nestingSuffix = iOpts.nestingSuffix
? utils.regexEscape(iOpts.nestingSuffix)
: iOpts.nestingSuffixEscaped || utils.regexEscape(')');
this.nestingOptionsSeparator = iOpts.nestingOptionsSeparator
? iOpts.nestingOptionsSeparator
: iOpts.nestingOptionsSeparator || ',';
this.maxReplaces = iOpts.maxReplaces ? iOpts.maxReplaces : 1000;
this.alwaysFormat = iOpts.alwaysFormat !== undefined ? iOpts.alwaysFormat : false;
// the regexp
this.resetRegExp();
}
reset() {
if (this.options) this.init(this.options);
}
resetRegExp() {
// the regexp
const regexpStr = `${this.prefix}(.+?)${this.suffix}`;
this.regexp = new RegExp(regexpStr, 'g');
const regexpUnescapeStr = `${this.prefix}${this.unescapePrefix}(.+?)${this.unescapeSuffix}${
this.suffix
}`;
this.regexpUnescape = new RegExp(regexpUnescapeStr, 'g');
const nestingRegexpStr = `${this.nestingPrefix}(.+?)${this.nestingSuffix}`;
this.nestingRegexp = new RegExp(nestingRegexpStr, 'g');
}
interpolate(str, data, lng, options) {
let match;
let value;
let replaces;
const defaultData =
(this.options && this.options.interpolation && this.options.interpolation.defaultVariables) ||
{};
function regexSafe(val) {
return val.replace(/\$/g, '$$$$');
}
const handleFormat = key => {
if (key.indexOf(this.formatSeparator) < 0) {
const path = utils.getPathWithDefaults(data, defaultData, key);
return this.alwaysFormat ? this.format(path, undefined, lng) : path;
}
const p = key.split(this.formatSeparator);
const k = p.shift().trim();
const f = p.join(this.formatSeparator).trim();
return this.format(utils.getPathWithDefaults(data, defaultData, k), f, lng, options);
};
this.resetRegExp();
const missingInterpolationHandler =
(options && options.missingInterpolationHandler) || this.options.missingInterpolationHandler;
replaces = 0;
// unescape if has unescapePrefix/Suffix
/* eslint no-cond-assign: 0 */
while ((match = this.regexpUnescape.exec(str))) {
value = handleFormat(match[1].trim());
if (value === undefined) {
if (typeof missingInterpolationHandler === 'function') {
const temp = missingInterpolationHandler(str, match, options);
value = typeof temp === 'string' ? temp : '';
} else {
this.logger.warn(`missed to pass in variable ${match[1]} for interpolating ${str}`);
value = '';
}
} else if (typeof value !== 'string' && !this.useRawValueToEscape) {
value = utils.makeString(value);
}
str = str.replace(match[0], regexSafe(value));
this.regexpUnescape.lastIndex = 0;
replaces++;
if (replaces >= this.maxReplaces) {
break;
}
}
replaces = 0;
// regular escape on demand
while ((match = this.regexp.exec(str))) {
value = handleFormat(match[1].trim());
if (value === undefined) {
if (typeof missingInterpolationHandler === 'function') {
const temp = missingInterpolationHandler(str, match, options);
value = typeof temp === 'string' ? temp : '';
} else {
this.logger.warn(`missed to pass in variable ${match[1]} for interpolating ${str}`);
value = '';
}
} else if (typeof value !== 'string' && !this.useRawValueToEscape) {
value = utils.makeString(value);
}
value = this.escapeValue ? regexSafe(this.escape(value)) : regexSafe(value);
str = str.replace(match[0], value);
this.regexp.lastIndex = 0;
replaces++;
if (replaces >= this.maxReplaces) {
break;
}
}
return str;
}
nest(str, fc, options = {}) {
let match;
let value;
let clonedOptions = { ...options };
clonedOptions.applyPostProcessor = false; // avoid post processing on nested lookup
delete clonedOptions.defaultValue; // assert we do not get a endless loop on interpolating defaultValue again and again
// if value is something like "myKey": "lorem $(anotherKey, { "count": {{aValueInOptions}} })"
function handleHasOptions(key, inheritedOptions) {
const sep = this.nestingOptionsSeparator;
if (key.indexOf(sep) < 0) return key;
const c = key.split(new RegExp(`${sep}[ ]*{`));
let optionsString = `{${c[1]}`;
key = c[0];
optionsString = this.interpolate(optionsString, clonedOptions);
optionsString = optionsString.replace(/'/g, '"');
try {
clonedOptions = JSON.parse(optionsString);
if (inheritedOptions) clonedOptions = { ...inheritedOptions, ...clonedOptions };
} catch (e) {
this.logger.warn(`failed parsing options string in nesting for key ${key}`, e);
return `${key}${sep}${optionsString}`;
}
// assert we do not get a endless loop on interpolating defaultValue again and again
delete clonedOptions.defaultValue;
return key;
}
// regular escape on demand
while ((match = this.nestingRegexp.exec(str))) {
let formatters = [];
/**
* If there is more than one parameter (contains the format separator). E.g.:
* - t(a, b)
* - t(a, b, c)
*
* And those parameters are not dynamic values (parameters do not include curly braces). E.g.:
* - Not t(a, { "key": "{{variable}}" })
* - Not t(a, b, {"keyA": "valueA", "keyB": "valueB"})
*/
let doReduce = false;
if (match[0].includes(this.formatSeparator) && !/{.*}/.test(match[1])) {
const r = match[1].split(this.formatSeparator).map(elem => elem.trim());
match[1] = r.shift();
formatters = r;
doReduce = true;
}
value = fc(handleHasOptions.call(this, match[1].trim(), clonedOptions), clonedOptions);
// is only the nesting key (key1 = '$(key2)') return the value without stringify
if (value && match[0] === str && typeof value !== 'string') return value;
// no string to include or empty
if (typeof value !== 'string') value = utils.makeString(value);
if (!value) {
this.logger.warn(`missed to resolve ${match[1]} for nesting ${str}`);
value = '';
}
if (doReduce) {
value = formatters.reduce((v, f) => this.format(v, f, options.lng, options), value.trim());
}
// Nested keys should not be escaped by default #854
// value = this.escapeValue ? regexSafe(utils.escape(value)) : regexSafe(value);
str = str.replace(match[0], value);
this.regexp.lastIndex = 0;
}
return str;
}
}
export default Interpolator;

View File

@@ -0,0 +1,170 @@
import baseLogger from './logger.js';
function capitalize(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
class LanguageUtil {
constructor(options) {
this.options = options;
// temporal backwards compatibility WHITELIST REMOVAL
this.whitelist = this.options.supportedLngs || false;
// end temporal backwards compatibility WHITELIST REMOVAL
this.supportedLngs = this.options.supportedLngs || false;
this.logger = baseLogger.create('languageUtils');
}
getScriptPartFromCode(code) {
if (!code || code.indexOf('-') < 0) return null;
const p = code.split('-');
if (p.length === 2) return null;
p.pop();
if (p[p.length - 1].toLowerCase() === 'x') return null;
return this.formatLanguageCode(p.join('-'));
}
getLanguagePartFromCode(code) {
if (!code || code.indexOf('-') < 0) return code;
const p = code.split('-');
return this.formatLanguageCode(p[0]);
}
formatLanguageCode(code) {
// http://www.iana.org/assignments/language-tags/language-tags.xhtml
if (typeof code === 'string' && code.indexOf('-') > -1) {
const specialCases = ['hans', 'hant', 'latn', 'cyrl', 'cans', 'mong', 'arab'];
let p = code.split('-');
if (this.options.lowerCaseLng) {
p = p.map(part => part.toLowerCase());
} else if (p.length === 2) {
p[0] = p[0].toLowerCase();
p[1] = p[1].toUpperCase();
if (specialCases.indexOf(p[1].toLowerCase()) > -1) p[1] = capitalize(p[1].toLowerCase());
} else if (p.length === 3) {
p[0] = p[0].toLowerCase();
// if lenght 2 guess it's a country
if (p[1].length === 2) p[1] = p[1].toUpperCase();
if (p[0] !== 'sgn' && p[2].length === 2) p[2] = p[2].toUpperCase();
if (specialCases.indexOf(p[1].toLowerCase()) > -1) p[1] = capitalize(p[1].toLowerCase());
if (specialCases.indexOf(p[2].toLowerCase()) > -1) p[2] = capitalize(p[2].toLowerCase());
}
return p.join('-');
}
return this.options.cleanCode || this.options.lowerCaseLng ? code.toLowerCase() : code;
}
// temporal backwards compatibility WHITELIST REMOVAL
isWhitelisted(code) {
this.logger.deprecate(
'languageUtils.isWhitelisted',
'function "isWhitelisted" will be renamed to "isSupportedCode" in the next major - please make sure to rename it\'s usage asap.',
);
return this.isSupportedCode(code);
}
// end temporal backwards compatibility WHITELIST REMOVAL
isSupportedCode(code) {
if (this.options.load === 'languageOnly' || this.options.nonExplicitSupportedLngs) {
code = this.getLanguagePartFromCode(code);
}
return (
!this.supportedLngs || !this.supportedLngs.length || this.supportedLngs.indexOf(code) > -1
);
}
getBestMatchFromCodes(codes) {
if (!codes) return null;
let found;
// pick first supported code or if no restriction pick the first one (highest prio)
codes.forEach(code => {
if (found) return;
let cleanedLng = this.formatLanguageCode(code);
if (!this.options.supportedLngs || this.isSupportedCode(cleanedLng)) found = cleanedLng;
});
// if we got no match in supportedLngs yet - check for similar locales
// first de-CH --> de
// second de-CH --> de-DE
if (!found && this.options.supportedLngs) {
codes.forEach(code => {
if (found) return;
let lngOnly = this.getLanguagePartFromCode(code);
if (this.isSupportedCode(lngOnly)) return (found = lngOnly);
found = this.options.supportedLngs.find(supportedLng => {
if (supportedLng.indexOf(lngOnly) === 0) return supportedLng;
});
});
}
// if nothing found, use fallbackLng
if (!found) found = this.getFallbackCodes(this.options.fallbackLng)[0];
return found;
}
getFallbackCodes(fallbacks, code) {
if (!fallbacks) return [];
if (typeof fallbacks === 'string') fallbacks = [fallbacks];
if (Object.prototype.toString.apply(fallbacks) === '[object Array]') return fallbacks;
if (!code) return fallbacks.default || [];
// asume we have an object defining fallbacks
let found = fallbacks[code];
if (!found) found = fallbacks[this.getScriptPartFromCode(code)];
if (!found) found = fallbacks[this.formatLanguageCode(code)];
if (!found) found = fallbacks[this.getLanguagePartFromCode(code)];
if (!found) found = fallbacks.default;
return found || [];
}
toResolveHierarchy(code, fallbackCode) {
const fallbackCodes = this.getFallbackCodes(
fallbackCode || this.options.fallbackLng || [],
code,
);
const codes = [];
const addCode = c => {
if (!c) return;
if (this.isSupportedCode(c)) {
codes.push(c);
} else {
this.logger.warn(`rejecting language code not found in supportedLngs: ${c}`);
}
};
if (typeof code === 'string' && code.indexOf('-') > -1) {
if (this.options.load !== 'languageOnly') addCode(this.formatLanguageCode(code));
if (this.options.load !== 'languageOnly' && this.options.load !== 'currentOnly')
addCode(this.getScriptPartFromCode(code));
if (this.options.load !== 'currentOnly') addCode(this.getLanguagePartFromCode(code));
} else if (typeof code === 'string') {
addCode(this.formatLanguageCode(code));
}
fallbackCodes.forEach(fc => {
if (codes.indexOf(fc) < 0) addCode(this.formatLanguageCode(fc));
});
return codes;
}
}
export default LanguageUtil;

View File

@@ -0,0 +1,160 @@
import baseLogger from './logger.js';
// definition http://translate.sourceforge.net/wiki/l10n/pluralforms
/* eslint-disable */
let sets = [
{ lngs: ['ach','ak','am','arn','br','fil','gun','ln','mfe','mg','mi','oc', 'pt', 'pt-BR',
'tg','ti','tr','uz','wa'], nr: [1,2], fc: 1 },
{ lngs: ['af','an','ast','az','bg','bn','ca','da','de','dev','el','en',
'eo','es','et','eu','fi','fo','fur','fy','gl','gu','ha','hi',
'hu','hy','ia','it','kn','ku','lb','mai','ml','mn','mr','nah','nap','nb',
'ne','nl','nn','no','nso','pa','pap','pms','ps','pt-PT','rm','sco',
'se','si','so','son','sq','sv','sw','ta','te','tk','ur','yo'], nr: [1,2], fc: 2 },
{ lngs: ['ay','bo','cgg','fa','id','ja','jbo','ka','kk','km','ko','ky','lo',
'ms','sah','su','th','tt','ug','vi','wo','zh'], nr: [1], fc: 3 },
{ lngs: ['be','bs', 'cnr', 'dz','hr','ru','sr','uk'], nr: [1,2,5], fc: 4 },
{ lngs: ['ar'], nr: [0,1,2,3,11,100], fc: 5 },
{ lngs: ['cs','sk'], nr: [1,2,5], fc: 6 },
{ lngs: ['csb','pl'], nr: [1,2,5], fc: 7 },
{ lngs: ['cy'], nr: [1,2,3,8], fc: 8 },
{ lngs: ['fr'], nr: [1,2], fc: 9 },
{ lngs: ['ga'], nr: [1,2,3,7,11], fc: 10 },
{ lngs: ['gd'], nr: [1,2,3,20], fc: 11 },
{ lngs: ['is'], nr: [1,2], fc: 12 },
{ lngs: ['jv'], nr: [0,1], fc: 13 },
{ lngs: ['kw'], nr: [1,2,3,4], fc: 14 },
{ lngs: ['lt'], nr: [1,2,10], fc: 15 },
{ lngs: ['lv'], nr: [1,2,0], fc: 16 },
{ lngs: ['mk'], nr: [1,2], fc: 17 },
{ lngs: ['mnk'], nr: [0,1,2], fc: 18 },
{ lngs: ['mt'], nr: [1,2,11,20], fc: 19 },
{ lngs: ['or'], nr: [2,1], fc: 2 },
{ lngs: ['ro'], nr: [1,2,20], fc: 20 },
{ lngs: ['sl'], nr: [5,1,2,3], fc: 21 },
{ lngs: ['he'], nr: [1,2,20,21], fc: 22 }
]
let _rulesPluralsTypes = {
1: function(n) {return Number(n > 1);},
2: function(n) {return Number(n != 1);},
3: function(n) {return 0;},
4: function(n) {return Number(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);},
5: function(n) {return Number(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5);},
6: function(n) {return Number((n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2);},
7: function(n) {return Number(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);},
8: function(n) {return Number((n==1) ? 0 : (n==2) ? 1 : (n != 8 && n != 11) ? 2 : 3);},
9: function(n) {return Number(n >= 2);},
10: function(n) {return Number(n==1 ? 0 : n==2 ? 1 : n<7 ? 2 : n<11 ? 3 : 4) ;},
11: function(n) {return Number((n==1 || n==11) ? 0 : (n==2 || n==12) ? 1 : (n > 2 && n < 20) ? 2 : 3);},
12: function(n) {return Number(n%10!=1 || n%100==11);},
13: function(n) {return Number(n !== 0);},
14: function(n) {return Number((n==1) ? 0 : (n==2) ? 1 : (n == 3) ? 2 : 3);},
15: function(n) {return Number(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2);},
16: function(n) {return Number(n%10==1 && n%100!=11 ? 0 : n !== 0 ? 1 : 2);},
17: function(n) {return Number(n==1 || n%10==1 ? 0 : 1);},
18: function(n) {return Number(n==0 ? 0 : n==1 ? 1 : 2);},
19: function(n) {return Number(n==1 ? 0 : n==0 || ( n%100>1 && n%100<11) ? 1 : (n%100>10 && n%100<20 ) ? 2 : 3);},
20: function(n) {return Number(n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2);},
21: function(n) {return Number(n%100==1 ? 1 : n%100==2 ? 2 : n%100==3 || n%100==4 ? 3 : 0); },
22: function(n) {return Number(n==1 ? 0 : n==2 ? 1 : (n<0 || n>10) && n%10==0 ? 2 : 3); }
};
/* eslint-enable */
function createRules() {
const rules = {};
sets.forEach((set) => {
set.lngs.forEach((l) => {
rules[l] = {
numbers: set.nr,
plurals: _rulesPluralsTypes[set.fc]
};
});
});
return rules;
}
class PluralResolver {
constructor(languageUtils, options = {}) {
this.languageUtils = languageUtils;
this.options = options;
this.logger = baseLogger.create('pluralResolver');
this.rules = createRules();
}
addRule(lng, obj) {
this.rules[lng] = obj;
}
getRule(code) {
return this.rules[code] || this.rules[this.languageUtils.getLanguagePartFromCode(code)];
}
needsPlural(code) {
const rule = this.getRule(code);
return rule && rule.numbers.length > 1;
}
getPluralFormsOfKey(code, key) {
const ret = [];
const rule = this.getRule(code);
if (!rule) return ret;
rule.numbers.forEach((n) => {
const suffix = this.getSuffix(code, n);
ret.push(`${key}${suffix}`);
});
return ret;
}
getSuffix(code, count) {
const rule = this.getRule(code);
if (rule) {
// if (rule.numbers.length === 1) return ''; // only singular
const idx = rule.noAbs ? rule.plurals(count) : rule.plurals(Math.abs(count));
let suffix = rule.numbers[idx];
// special treatment for lngs only having singular and plural
if (this.options.simplifyPluralSuffix && rule.numbers.length === 2 && rule.numbers[0] === 1) {
if (suffix === 2) {
suffix = 'plural';
} else if (suffix === 1) {
suffix = '';
}
}
const returnSuffix = () => (
this.options.prepend && suffix.toString() ? this.options.prepend + suffix.toString() : suffix.toString()
);
// COMPATIBILITY JSON
// v1
if (this.options.compatibilityJSON === 'v1') {
if (suffix === 1) return '';
if (typeof suffix === 'number') return `_plural_${suffix.toString()}`;
return returnSuffix();
} else if (/* v2 */ this.options.compatibilityJSON === 'v2') {
return returnSuffix();
} else if (/* v3 - gettext index */ this.options.simplifyPluralSuffix && rule.numbers.length === 2 && rule.numbers[0] === 1) {
return returnSuffix();
}
return this.options.prepend && idx.toString() ? this.options.prepend + idx.toString() : idx.toString();
}
this.logger.warn(`no plural rule found for: ${code}`);
return '';
}
}
export default PluralResolver;

View File

@@ -0,0 +1,9 @@
# i18next: learn once - translate everywhere
## visit ➡️ [i18next.com](https://www.i18next.com)
```js
import i18next from 'https://deno.land/x/i18next/index.js';
// or import i18next from 'https://raw.githubusercontent.com/i18next/i18next/master/src/index.js'
// or import i18next from 'https://cdn.jsdelivr.net/gh/i18next/i18next/src/index.js'
```

View File

@@ -0,0 +1,134 @@
import EventEmitter from './EventEmitter.js';
import * as utils from './utils.js';
class ResourceStore extends EventEmitter {
constructor(data, options = { ns: ['translation'], defaultNS: 'translation' }) {
super();
if (utils.isIE10) {
EventEmitter.call(this); // <=IE10 fix (unable to call parent constructor)
}
this.data = data || {};
this.options = options;
if (this.options.keySeparator === undefined) {
this.options.keySeparator = '.';
}
}
addNamespaces(ns) {
if (this.options.ns.indexOf(ns) < 0) {
this.options.ns.push(ns);
}
}
removeNamespaces(ns) {
const index = this.options.ns.indexOf(ns);
if (index > -1) {
this.options.ns.splice(index, 1);
}
}
getResource(lng, ns, key, options = {}) {
const keySeparator =
options.keySeparator !== undefined ? options.keySeparator : this.options.keySeparator;
let path = [lng, ns];
if (key && typeof key !== 'string') path = path.concat(key);
if (key && typeof key === 'string')
path = path.concat(keySeparator ? key.split(keySeparator) : key);
if (lng.indexOf('.') > -1) {
path = lng.split('.');
}
return utils.getPath(this.data, path);
}
addResource(lng, ns, key, value, options = { silent: false }) {
let keySeparator = this.options.keySeparator;
if (keySeparator === undefined) keySeparator = '.';
let path = [lng, ns];
if (key) path = path.concat(keySeparator ? key.split(keySeparator) : key);
if (lng.indexOf('.') > -1) {
path = lng.split('.');
value = ns;
ns = path[1];
}
this.addNamespaces(ns);
utils.setPath(this.data, path, value);
if (!options.silent) this.emit('added', lng, ns, key, value);
}
addResources(lng, ns, resources, options = { silent: false }) {
/* eslint no-restricted-syntax: 0 */
for (const m in resources) {
if (
typeof resources[m] === 'string' ||
Object.prototype.toString.apply(resources[m]) === '[object Array]'
)
this.addResource(lng, ns, m, resources[m], { silent: true });
}
if (!options.silent) this.emit('added', lng, ns, resources);
}
addResourceBundle(lng, ns, resources, deep, overwrite, options = { silent: false }) {
let path = [lng, ns];
if (lng.indexOf('.') > -1) {
path = lng.split('.');
deep = resources;
resources = ns;
ns = path[1];
}
this.addNamespaces(ns);
let pack = utils.getPath(this.data, path) || {};
if (deep) {
utils.deepExtend(pack, resources, overwrite);
} else {
pack = { ...pack, ...resources };
}
utils.setPath(this.data, path, pack);
if (!options.silent) this.emit('added', lng, ns, resources);
}
removeResourceBundle(lng, ns) {
if (this.hasResourceBundle(lng, ns)) {
delete this.data[lng][ns];
}
this.removeNamespaces(ns);
this.emit('removed', lng, ns);
}
hasResourceBundle(lng, ns) {
return this.getResource(lng, ns) !== undefined;
}
getResourceBundle(lng, ns) {
if (!ns) ns = this.options.defaultNS;
// COMPATIBILITY: remove extend in v2.1.0
if (this.options.compatibilityAPI === 'v1') return { ...{}, ...this.getResource(lng, ns) };
return this.getResource(lng, ns);
}
getDataByLanguage(lng) {
return this.data[lng];
}
toJSON() {
return this.data;
}
}
export default ResourceStore;

View File

@@ -0,0 +1,425 @@
import baseLogger from './logger.js';
import EventEmitter from './EventEmitter.js';
import postProcessor from './postProcessor.js';
import * as utils from './utils.js';
const checkedLoadedFor = {};
class Translator extends EventEmitter {
constructor(services, options = {}) {
super();
if (utils.isIE10) {
EventEmitter.call(this); // <=IE10 fix (unable to call parent constructor)
}
utils.copy(
[
'resourceStore',
'languageUtils',
'pluralResolver',
'interpolator',
'backendConnector',
'i18nFormat',
'utils',
],
services,
this,
);
this.options = options;
if (this.options.keySeparator === undefined) {
this.options.keySeparator = '.';
}
this.logger = baseLogger.create('translator');
}
changeLanguage(lng) {
if (lng) this.language = lng;
}
exists(key, options = { interpolation: {} }) {
const resolved = this.resolve(key, options);
return resolved && resolved.res !== undefined;
}
extractFromKey(key, options) {
let nsSeparator = options.nsSeparator || this.options.nsSeparator;
if (nsSeparator === undefined) nsSeparator = ':';
const keySeparator =
options.keySeparator !== undefined ? options.keySeparator : this.options.keySeparator;
let namespaces = options.ns || this.options.defaultNS;
if (nsSeparator && key.indexOf(nsSeparator) > -1) {
const parts = key.split(nsSeparator);
if (
nsSeparator !== keySeparator ||
(nsSeparator === keySeparator && this.options.ns.indexOf(parts[0]) > -1)
)
namespaces = parts.shift();
key = parts.join(keySeparator);
}
if (typeof namespaces === 'string') namespaces = [namespaces];
return {
key,
namespaces,
};
}
translate(keys, options) {
if (typeof options !== 'object' && this.options.overloadTranslationOptionHandler) {
/* eslint prefer-rest-params: 0 */
options = this.options.overloadTranslationOptionHandler(arguments);
}
if (!options) options = {};
// non valid keys handling
if (keys === undefined || keys === null /* || keys === ''*/) return '';
if (!Array.isArray(keys)) keys = [String(keys)];
// separators
const keySeparator =
options.keySeparator !== undefined ? options.keySeparator : this.options.keySeparator;
// get namespace(s)
const { key, namespaces } = this.extractFromKey(keys[keys.length - 1], options);
const namespace = namespaces[namespaces.length - 1];
// return key on CIMode
const lng = options.lng || this.language;
const appendNamespaceToCIMode =
options.appendNamespaceToCIMode || this.options.appendNamespaceToCIMode;
if (lng && lng.toLowerCase() === 'cimode') {
if (appendNamespaceToCIMode) {
const nsSeparator = options.nsSeparator || this.options.nsSeparator;
return namespace + nsSeparator + key;
}
return key;
}
// resolve from store
const resolved = this.resolve(keys, options);
let res = resolved && resolved.res;
const resUsedKey = (resolved && resolved.usedKey) || key;
const resExactUsedKey = (resolved && resolved.exactUsedKey) || key;
const resType = Object.prototype.toString.apply(res);
const noObject = ['[object Number]', '[object Function]', '[object RegExp]'];
const joinArrays =
options.joinArrays !== undefined ? options.joinArrays : this.options.joinArrays;
// object
const handleAsObjectInI18nFormat = !this.i18nFormat || this.i18nFormat.handleAsObject;
const handleAsObject =
typeof res !== 'string' && typeof res !== 'boolean' && typeof res !== 'number';
if (
handleAsObjectInI18nFormat &&
res &&
handleAsObject &&
noObject.indexOf(resType) < 0 &&
!(typeof joinArrays === 'string' && resType === '[object Array]')
) {
if (!options.returnObjects && !this.options.returnObjects) {
this.logger.warn('accessing an object - but returnObjects options is not enabled!');
return this.options.returnedObjectHandler
? this.options.returnedObjectHandler(resUsedKey, res, options)
: `key '${key} (${this.language})' returned an object instead of string.`;
}
// if we got a separator we loop over children - else we just return object as is
// as having it set to false means no hierarchy so no lookup for nested values
if (keySeparator) {
const resTypeIsArray = resType === '[object Array]';
const copy = resTypeIsArray ? [] : {}; // apply child translation on a copy
/* eslint no-restricted-syntax: 0 */
let newKeyToUse = resTypeIsArray ? resExactUsedKey : resUsedKey;
for (const m in res) {
if (Object.prototype.hasOwnProperty.call(res, m)) {
const deepKey = `${newKeyToUse}${keySeparator}${m}`;
copy[m] = this.translate(deepKey, {
...options,
...{ joinArrays: false, ns: namespaces },
});
if (copy[m] === deepKey) copy[m] = res[m]; // if nothing found use orginal value as fallback
}
}
res = copy;
}
} else if (
handleAsObjectInI18nFormat &&
typeof joinArrays === 'string' &&
resType === '[object Array]'
) {
// array special treatment
res = res.join(joinArrays);
if (res) res = this.extendTranslation(res, keys, options);
} else {
// string, empty or null
let usedDefault = false;
let usedKey = false;
// fallback value
if (!this.isValidLookup(res) && options.defaultValue !== undefined) {
usedDefault = true;
if (options.count !== undefined) {
const suffix = this.pluralResolver.getSuffix(lng, options.count);
res = options[`defaultValue${suffix}`];
}
if (!res) res = options.defaultValue;
}
if (!this.isValidLookup(res)) {
usedKey = true;
res = key;
}
// save missing
const updateMissing =
options.defaultValue && options.defaultValue !== res && this.options.updateMissing;
if (usedKey || usedDefault || updateMissing) {
this.logger.log(
updateMissing ? 'updateKey' : 'missingKey',
lng,
namespace,
key,
updateMissing ? options.defaultValue : res,
);
if (keySeparator) {
const fk = this.resolve(key, { ...options, keySeparator: false });
if (fk && fk.res) this.logger.warn('Seems the loaded translations were in flat JSON format instead of nested. Either set keySeparator: false on init or make sure your translations are published in nested format.');
}
let lngs = [];
const fallbackLngs = this.languageUtils.getFallbackCodes(
this.options.fallbackLng,
options.lng || this.language,
);
if (this.options.saveMissingTo === 'fallback' && fallbackLngs && fallbackLngs[0]) {
for (let i = 0; i < fallbackLngs.length; i++) {
lngs.push(fallbackLngs[i]);
}
} else if (this.options.saveMissingTo === 'all') {
lngs = this.languageUtils.toResolveHierarchy(options.lng || this.language);
} else {
lngs.push(options.lng || this.language);
}
const send = (l, k) => {
if (this.options.missingKeyHandler) {
this.options.missingKeyHandler(
l,
namespace,
k,
updateMissing ? options.defaultValue : res,
updateMissing,
options,
);
} else if (this.backendConnector && this.backendConnector.saveMissing) {
this.backendConnector.saveMissing(
l,
namespace,
k,
updateMissing ? options.defaultValue : res,
updateMissing,
options,
);
}
this.emit('missingKey', l, namespace, k, res);
};
if (this.options.saveMissing) {
const needsPluralHandling =
options.count !== undefined && typeof options.count !== 'string';
if (this.options.saveMissingPlurals && needsPluralHandling) {
lngs.forEach(l => {
const plurals = this.pluralResolver.getPluralFormsOfKey(l, key);
plurals.forEach(p => send([l], p));
});
} else {
send(lngs, key);
}
}
}
// extend
res = this.extendTranslation(res, keys, options, resolved);
// append namespace if still key
if (usedKey && res === key && this.options.appendNamespaceToMissingKey)
res = `${namespace}:${key}`;
// parseMissingKeyHandler
if (usedKey && this.options.parseMissingKeyHandler)
res = this.options.parseMissingKeyHandler(res);
}
// return
return res;
}
extendTranslation(res, key, options, resolved) {
if (this.i18nFormat && this.i18nFormat.parse) {
res = this.i18nFormat.parse(
res,
options,
resolved.usedLng,
resolved.usedNS,
resolved.usedKey,
{ resolved },
);
} else if (!options.skipInterpolation) {
// i18next.parsing
if (options.interpolation)
this.interpolator.init({
...options,
...{ interpolation: { ...this.options.interpolation, ...options.interpolation } },
});
// interpolate
let data = options.replace && typeof options.replace !== 'string' ? options.replace : options;
if (this.options.interpolation.defaultVariables)
data = { ...this.options.interpolation.defaultVariables, ...data };
res = this.interpolator.interpolate(res, data, options.lng || this.language, options);
// nesting
if (options.nest !== false)
res = this.interpolator.nest(res, (...args) => this.translate(...args), options);
if (options.interpolation) this.interpolator.reset();
}
// post process
const postProcess = options.postProcess || this.options.postProcess;
const postProcessorNames = typeof postProcess === 'string' ? [postProcess] : postProcess;
if (
res !== undefined &&
res !== null &&
postProcessorNames &&
postProcessorNames.length &&
options.applyPostProcessor !== false
) {
res = postProcessor.handle(
postProcessorNames,
res,
key,
this.options && this.options.postProcessPassResolved
? { i18nResolved: resolved, ...options }
: options,
this,
);
}
return res;
}
resolve(keys, options = {}) {
let found;
let usedKey; // plain key
let exactUsedKey; // key with context / plural
let usedLng;
let usedNS;
if (typeof keys === 'string') keys = [keys];
// forEach possible key
keys.forEach(k => {
if (this.isValidLookup(found)) return;
const extracted = this.extractFromKey(k, options);
const key = extracted.key;
usedKey = key;
let namespaces = extracted.namespaces;
if (this.options.fallbackNS) namespaces = namespaces.concat(this.options.fallbackNS);
const needsPluralHandling = options.count !== undefined && typeof options.count !== 'string';
const needsContextHandling =
options.context !== undefined &&
typeof options.context === 'string' &&
options.context !== '';
const codes = options.lngs
? options.lngs
: this.languageUtils.toResolveHierarchy(options.lng || this.language, options.fallbackLng);
namespaces.forEach(ns => {
if (this.isValidLookup(found)) return;
usedNS = ns;
if (
!checkedLoadedFor[`${codes[0]}-${ns}`] &&
this.utils &&
this.utils.hasLoadedNamespace &&
!this.utils.hasLoadedNamespace(usedNS)
) {
checkedLoadedFor[`${codes[0]}-${ns}`] = true;
this.logger.warn(
`key "${usedKey}" for languages "${codes.join(
', ',
)}" won't get resolved as namespace "${usedNS}" was not yet loaded`,
'This means something IS WRONG in your setup. You access the t function before i18next.init / i18next.loadNamespace / i18next.changeLanguage was done. Wait for the callback or Promise to resolve before accessing it!!!',
);
}
codes.forEach(code => {
if (this.isValidLookup(found)) return;
usedLng = code;
let finalKey = key;
const finalKeys = [finalKey];
if (this.i18nFormat && this.i18nFormat.addLookupKeys) {
this.i18nFormat.addLookupKeys(finalKeys, key, code, ns, options);
} else {
let pluralSuffix;
if (needsPluralHandling)
pluralSuffix = this.pluralResolver.getSuffix(code, options.count);
// fallback for plural if context not found
if (needsPluralHandling && needsContextHandling)
finalKeys.push(finalKey + pluralSuffix);
// get key for context if needed
if (needsContextHandling)
finalKeys.push((finalKey += `${this.options.contextSeparator}${options.context}`));
// get key for plural if needed
if (needsPluralHandling) finalKeys.push((finalKey += pluralSuffix));
}
// iterate over finalKeys starting with most specific pluralkey (-> contextkey only) -> singularkey only
let possibleKey;
/* eslint no-cond-assign: 0 */
while ((possibleKey = finalKeys.pop())) {
if (!this.isValidLookup(found)) {
exactUsedKey = possibleKey;
found = this.getResource(code, ns, possibleKey, options);
}
}
});
});
});
return { res: found, usedKey, exactUsedKey, usedLng, usedNS };
}
isValidLookup(res) {
return (
res !== undefined &&
!(!this.options.returnNull && res === null) &&
!(!this.options.returnEmptyString && res === '')
);
}
getResource(code, ns, key, options = {}) {
if (this.i18nFormat && this.i18nFormat.getResource)
return this.i18nFormat.getResource(code, ns, key, options);
return this.resourceStore.getResource(code, ns, key, options);
}
}
export default Translator;

View File

@@ -0,0 +1,107 @@
export function get() {
return {
debug: false,
initImmediate: true,
ns: ['translation'],
defaultNS: ['translation'],
fallbackLng: ['dev'],
fallbackNS: false, // string or array of namespaces
// temporal backwards compatibility WHITELIST REMOVAL
whitelist: false, // array with supported languages
nonExplicitWhitelist: false,
// end temporal backwards compatibility WHITELIST REMOVAL
supportedLngs: false, // array with supported languages
nonExplicitSupportedLngs: false,
load: 'all', // | currentOnly | languageOnly
preload: false, // array with preload languages
simplifyPluralSuffix: true,
keySeparator: '.',
nsSeparator: ':',
pluralSeparator: '_',
contextSeparator: '_',
partialBundledLanguages: false, // allow bundling certain languages that are not remotely fetched
saveMissing: false, // enable to send missing values
updateMissing: false, // enable to update default values if different from translated value (only useful on initial development, or when keeping code as source of truth)
saveMissingTo: 'fallback', // 'current' || 'all'
saveMissingPlurals: true, // will save all forms not only singular key
missingKeyHandler: false, // function(lng, ns, key, fallbackValue) -> override if prefer on handling
missingInterpolationHandler: false, // function(str, match)
postProcess: false, // string or array of postProcessor names
postProcessPassResolved: false, // pass resolved object into 'options.i18nResolved' for postprocessor
returnNull: true, // allows null value as valid translation
returnEmptyString: true, // allows empty string value as valid translation
returnObjects: false,
joinArrays: false, // or string to join array
returnedObjectHandler: false, // function(key, value, options) triggered if key returns object but returnObjects is set to false
parseMissingKeyHandler: false, // function(key) parsed a key that was not found in t() before returning
appendNamespaceToMissingKey: false,
appendNamespaceToCIMode: false,
overloadTranslationOptionHandler: function handle(args) {
var ret = {};
if (typeof args[1] === 'object') ret = args[1];
if (typeof args[1] === 'string') ret.defaultValue = args[1];
if (typeof args[2] === 'string') ret.tDescription = args[2];
if (typeof args[2] === 'object' || typeof args[3] === 'object') {
var options = args[3] || args[2];
Object.keys(options).forEach(function(key) {
ret[key] = options[key];
});
}
return ret;
},
interpolation: {
escapeValue: true,
format: (value, format, lng, options) => value,
prefix: '{{',
suffix: '}}',
formatSeparator: ',',
// prefixEscaped: '{{',
// suffixEscaped: '}}',
// unescapeSuffix: '',
unescapePrefix: '-',
nestingPrefix: '$t(',
nestingSuffix: ')',
nestingOptionsSeparator: ',',
// nestingPrefixEscaped: '$t(',
// nestingSuffixEscaped: ')',
// defaultVariables: undefined // object that can have values to interpolate on - extends passed in interpolation data
maxReplaces: 1000, // max replaces to prevent endless loop
},
};
}
/* eslint no-param-reassign: 0 */
export function transformOptions(options) {
// create namespace object if namespace is passed in as string
if (typeof options.ns === 'string') options.ns = [options.ns];
if (typeof options.fallbackLng === 'string') options.fallbackLng = [options.fallbackLng];
if (typeof options.fallbackNS === 'string') options.fallbackNS = [options.fallbackNS];
// temporal backwards compatibility WHITELIST REMOVAL
if (options.whitelist) {
if (options.whitelist && options.whitelist.indexOf('cimode') < 0) {
options.whitelist = options.whitelist.concat(['cimode']);
}
options.supportedLngs = options.whitelist;
}
if (options.nonExplicitWhitelist) {
options.nonExplicitSupportedLngs = options.nonExplicitWhitelist;
}
// end temporal backwards compatibility WHITELIST REMOVAL
// extend supportedLngs with cimode
if (options.supportedLngs && options.supportedLngs.indexOf('cimode') < 0) {
options.supportedLngs = options.supportedLngs.concat(['cimode']);
}
return options;
}

View File

@@ -0,0 +1,516 @@
import baseLogger from './logger.js';
import EventEmitter from './EventEmitter.js';
import ResourceStore from './ResourceStore.js';
import Translator from './Translator.js';
import LanguageUtils from './LanguageUtils.js';
import PluralResolver from './PluralResolver.js';
import Interpolator from './Interpolator.js';
import BackendConnector from './BackendConnector.js';
import { get as getDefaults, transformOptions } from './defaults.js';
import postProcessor from './postProcessor.js';
import { defer, isIE10 } from './utils.js';
function noop() { }
class I18n extends EventEmitter {
constructor(options = {}, callback) {
super();
if (isIE10) {
EventEmitter.call(this) // <=IE10 fix (unable to call parent constructor)
}
this.options = transformOptions(options);
this.services = {};
this.logger = baseLogger;
this.modules = { external: [] };
if (callback && !this.isInitialized && !options.isClone) {
// https://github.com/i18next/i18next/issues/879
if (!this.options.initImmediate) {
this.init(options, callback);
return this;
}
setTimeout(() => {
this.init(options, callback);
}, 0);
}
}
init(options = {}, callback) {
if (typeof options === 'function') {
callback = options;
options = {};
}
// temporal backwards compatibility WHITELIST REMOVAL
if (options.whitelist && !options.supportedLngs) {
this.logger.deprecate('whitelist', 'option "whitelist" will be renamed to "supportedLngs" in the next major - please make sure to rename this option asap.');
}
if (options.nonExplicitWhitelist && !options.nonExplicitSupportedLngs) {
this.logger.deprecate('whitelist', 'options "nonExplicitWhitelist" will be renamed to "nonExplicitSupportedLngs" in the next major - please make sure to rename this option asap.');
}
// end temporal backwards compatibility WHITELIST REMOVAL
this.options = { ...getDefaults(), ...this.options, ...transformOptions(options) };
this.format = this.options.interpolation.format;
if (!callback) callback = noop;
function createClassOnDemand(ClassOrObject) {
if (!ClassOrObject) return null;
if (typeof ClassOrObject === 'function') return new ClassOrObject();
return ClassOrObject;
}
// init services
if (!this.options.isClone) {
if (this.modules.logger) {
baseLogger.init(createClassOnDemand(this.modules.logger), this.options);
} else {
baseLogger.init(null, this.options);
}
const lu = new LanguageUtils(this.options);
this.store = new ResourceStore(this.options.resources, this.options);
const s = this.services;
s.logger = baseLogger;
s.resourceStore = this.store;
s.languageUtils = lu;
s.pluralResolver = new PluralResolver(lu, {
prepend: this.options.pluralSeparator,
compatibilityJSON: this.options.compatibilityJSON,
simplifyPluralSuffix: this.options.simplifyPluralSuffix,
});
s.interpolator = new Interpolator(this.options);
s.utils = {
hasLoadedNamespace: this.hasLoadedNamespace.bind(this)
}
s.backendConnector = new BackendConnector(
createClassOnDemand(this.modules.backend),
s.resourceStore,
s,
this.options,
);
// pipe events from backendConnector
s.backendConnector.on('*', (event, ...args) => {
this.emit(event, ...args);
});
if (this.modules.languageDetector) {
s.languageDetector = createClassOnDemand(this.modules.languageDetector);
s.languageDetector.init(s, this.options.detection, this.options);
}
if (this.modules.i18nFormat) {
s.i18nFormat = createClassOnDemand(this.modules.i18nFormat);
if (s.i18nFormat.init) s.i18nFormat.init(this);
}
this.translator = new Translator(this.services, this.options);
// pipe events from translator
this.translator.on('*', (event, ...args) => {
this.emit(event, ...args);
});
this.modules.external.forEach(m => {
if (m.init) m.init(this);
});
}
if (!this.modules.languageDetector && !this.options.lng) {
this.logger.warn('init: no languageDetector is used and no lng is defined');
}
// append api
const storeApi = [
'getResource',
'addResource',
'addResources',
'addResourceBundle',
'removeResourceBundle',
'hasResourceBundle',
'getResourceBundle',
'getDataByLanguage',
];
storeApi.forEach(fcName => {
this[fcName] = (...args) => this.store[fcName](...args);
});
const deferred = defer();
const load = () => {
this.changeLanguage(this.options.lng, (err, t) => {
this.isInitialized = true;
this.logger.log('initialized', this.options);
this.emit('initialized', this.options);
deferred.resolve(t); // not rejecting on err (as err is only a loading translation failed warning)
callback(err, t);
});
};
if (this.options.resources || !this.options.initImmediate) {
load();
} else {
setTimeout(load, 0);
}
return deferred;
}
/* eslint consistent-return: 0 */
loadResources(language, callback = noop) {
let usedCallback = callback;
let usedLng = typeof language === 'string' ? language : this.language;
if (typeof language === 'function') usedCallback = language;
if (!this.options.resources || this.options.partialBundledLanguages) {
if (usedLng && usedLng.toLowerCase() === 'cimode') return usedCallback(); // avoid loading resources for cimode
const toLoad = [];
const append = lng => {
if (!lng) return;
const lngs = this.services.languageUtils.toResolveHierarchy(lng);
lngs.forEach(l => {
if (toLoad.indexOf(l) < 0) toLoad.push(l);
});
};
if (!usedLng) {
// at least load fallbacks in this case
const fallbacks = this.services.languageUtils.getFallbackCodes(this.options.fallbackLng);
fallbacks.forEach(l => append(l));
} else {
append(usedLng);
}
if (this.options.preload) {
this.options.preload.forEach(l => append(l));
}
this.services.backendConnector.load(toLoad, this.options.ns, usedCallback);
} else {
usedCallback(null);
}
}
reloadResources(lngs, ns, callback) {
const deferred = defer();
if (!lngs) lngs = this.languages;
if (!ns) ns = this.options.ns;
if (!callback) callback = noop;
this.services.backendConnector.reload(lngs, ns, err => {
deferred.resolve(); // not rejecting on err (as err is only a loading translation failed warning)
callback(err);
});
return deferred;
}
use(module) {
if (!module) throw new Error('You are passing an undefined module! Please check the object you are passing to i18next.use()')
if (!module.type) throw new Error('You are passing a wrong module! Please check the object you are passing to i18next.use()')
if (module.type === 'backend') {
this.modules.backend = module;
}
if (module.type === 'logger' || (module.log && module.warn && module.error)) {
this.modules.logger = module;
}
if (module.type === 'languageDetector') {
this.modules.languageDetector = module;
}
if (module.type === 'i18nFormat') {
this.modules.i18nFormat = module;
}
if (module.type === 'postProcessor') {
postProcessor.addPostProcessor(module);
}
if (module.type === '3rdParty') {
this.modules.external.push(module);
}
return this;
}
changeLanguage(lng, callback) {
this.isLanguageChangingTo = lng;
const deferred = defer();
this.emit('languageChanging', lng);
const done = (err, l) => {
if (l) {
this.language = l;
this.languages = this.services.languageUtils.toResolveHierarchy(l);
this.translator.changeLanguage(l);
this.isLanguageChangingTo = undefined;
this.emit('languageChanged', l);
this.logger.log('languageChanged', l);
} else {
this.isLanguageChangingTo = undefined;
}
deferred.resolve((...args) => this.t(...args));
if (callback) callback(err, (...args) => this.t(...args));
};
const setLng = lngs => {
// depending on API in detector lng can be a string (old) or an array of languages ordered in priority
const l = typeof lngs === 'string' ? lngs : this.services.languageUtils.getBestMatchFromCodes(lngs);
if (l) {
if (!this.language) {
this.language = l;
this.languages = this.services.languageUtils.toResolveHierarchy(l);
}
if (!this.translator.language) this.translator.changeLanguage(l);
if (this.services.languageDetector) this.services.languageDetector.cacheUserLanguage(l);
}
this.loadResources(l, err => {
done(err, l);
});
};
if (!lng && this.services.languageDetector && !this.services.languageDetector.async) {
setLng(this.services.languageDetector.detect());
} else if (!lng && this.services.languageDetector && this.services.languageDetector.async) {
this.services.languageDetector.detect(setLng);
} else {
setLng(lng);
}
return deferred;
}
getFixedT(lng, ns) {
const fixedT = (key, opts, ...rest) => {
let options;
if (typeof opts !== 'object') {
options = this.options.overloadTranslationOptionHandler([key, opts].concat(rest));
} else {
options = { ...opts };
}
options.lng = options.lng || fixedT.lng;
options.lngs = options.lngs || fixedT.lngs;
options.ns = options.ns || fixedT.ns;
return this.t(key, options);
};
if (typeof lng === 'string') {
fixedT.lng = lng;
} else {
fixedT.lngs = lng;
}
fixedT.ns = ns;
return fixedT;
}
t(...args) {
return this.translator && this.translator.translate(...args);
}
exists(...args) {
return this.translator && this.translator.exists(...args);
}
setDefaultNamespace(ns) {
this.options.defaultNS = ns;
}
hasLoadedNamespace(ns, options = {}) {
if (!this.isInitialized) {
this.logger.warn('hasLoadedNamespace: i18next was not initialized', this.languages);
return false;
}
if (!this.languages || !this.languages.length) {
this.logger.warn('hasLoadedNamespace: i18n.languages were undefined or empty', this.languages);
return false;
}
const lng = this.languages[0];
const fallbackLng = this.options ? this.options.fallbackLng : false;
const lastLng = this.languages[this.languages.length - 1];
// we're in cimode so this shall pass
if (lng.toLowerCase() === 'cimode') return true;
const loadNotPending = (l, n) => {
const loadState = this.services.backendConnector.state[`${l}|${n}`];
return loadState === -1 || loadState === 2;
};
// optional injected check
if (options.precheck) {
const preResult = options.precheck(this, loadNotPending);
if (preResult !== undefined) return preResult;
}
// loaded -> SUCCESS
if (this.hasResourceBundle(lng, ns)) return true;
// were not loading at all -> SEMI SUCCESS
if (!this.services.backendConnector.backend) return true;
// failed loading ns - but at least fallback is not pending -> SEMI SUCCESS
if (loadNotPending(lng, ns) && (!fallbackLng || loadNotPending(lastLng, ns))) return true;
return false;
}
loadNamespaces(ns, callback) {
const deferred = defer();
if (!this.options.ns) {
callback && callback();
return Promise.resolve();
}
if (typeof ns === 'string') ns = [ns];
ns.forEach(n => {
if (this.options.ns.indexOf(n) < 0) this.options.ns.push(n);
});
this.loadResources(err => {
deferred.resolve();
if (callback) callback(err);
});
return deferred;
}
loadLanguages(lngs, callback) {
const deferred = defer();
if (typeof lngs === 'string') lngs = [lngs];
const preloaded = this.options.preload || [];
const newLngs = lngs.filter(lng => preloaded.indexOf(lng) < 0);
// Exit early if all given languages are already preloaded
if (!newLngs.length) {
if (callback) callback();
return Promise.resolve();
}
this.options.preload = preloaded.concat(newLngs);
this.loadResources(err => {
deferred.resolve();
if (callback) callback(err);
});
return deferred;
}
dir(lng) {
if (!lng) lng = this.languages && this.languages.length > 0 ? this.languages[0] : this.language;
if (!lng) return 'rtl';
const rtlLngs = [
'ar',
'shu',
'sqr',
'ssh',
'xaa',
'yhd',
'yud',
'aao',
'abh',
'abv',
'acm',
'acq',
'acw',
'acx',
'acy',
'adf',
'ads',
'aeb',
'aec',
'afb',
'ajp',
'apc',
'apd',
'arb',
'arq',
'ars',
'ary',
'arz',
'auz',
'avl',
'ayh',
'ayl',
'ayn',
'ayp',
'bbz',
'pga',
'he',
'iw',
'ps',
'pbt',
'pbu',
'pst',
'prp',
'prd',
'ug',
'ur',
'ydd',
'yds',
'yih',
'ji',
'yi',
'hbo',
'men',
'xmn',
'fa',
'jpr',
'peo',
'pes',
'prs',
'dv',
'sam',
];
return rtlLngs.indexOf(this.services.languageUtils.getLanguagePartFromCode(lng)) >= 0
? 'rtl'
: 'ltr';
}
/* eslint class-methods-use-this: 0 */
createInstance(options = {}, callback) {
return new I18n(options, callback);
}
cloneInstance(options = {}, callback = noop) {
const mergedOptions = { ...this.options, ...options, ...{ isClone: true } };
const clone = new I18n(mergedOptions);
const membersToCopy = ['store', 'services', 'language'];
membersToCopy.forEach(m => {
clone[m] = this[m];
});
clone.services = { ...this.services };
clone.services.utils = {
hasLoadedNamespace: clone.hasLoadedNamespace.bind(clone)
};
clone.translator = new Translator(clone.services, clone.options);
clone.translator.on('*', (event, ...args) => {
clone.emit(event, ...args);
});
clone.init(mergedOptions, callback);
clone.translator.options = clone.options; // sync options
clone.translator.backendConnector.services.utils = {
hasLoadedNamespace: clone.hasLoadedNamespace.bind(clone)
};
return clone;
}
}
export default new I18n();

View File

@@ -0,0 +1,3 @@
import i18next from './i18next.js';
export default i18next;

View File

@@ -0,0 +1,68 @@
const consoleLogger = {
type: 'logger',
log(args) {
this.output('log', args);
},
warn(args) {
this.output('warn', args);
},
error(args) {
this.output('error', args);
},
output(type, args) {
/* eslint no-console: 0 */
if (console && console[type]) console[type].apply(console, args);
},
};
class Logger {
constructor(concreteLogger, options = {}) {
this.init(concreteLogger, options);
}
init(concreteLogger, options = {}) {
this.prefix = options.prefix || 'i18next:';
this.logger = concreteLogger || consoleLogger;
this.options = options;
this.debug = options.debug;
}
setDebug(bool) {
this.debug = bool;
}
log(...args) {
return this.forward(args, 'log', '', true);
}
warn(...args) {
return this.forward(args, 'warn', '', true);
}
error(...args) {
return this.forward(args, 'error', '');
}
deprecate(...args) {
return this.forward(args, 'warn', 'WARNING DEPRECATED: ', true);
}
forward(args, lvl, prefix, debugOnly) {
if (debugOnly && !this.debug) return null;
if (typeof args[0] === 'string') args[0] = `${prefix}${this.prefix} ${args[0]}`;
return this.logger[lvl](args);
}
create(moduleName) {
return new Logger(this.logger, {
...{ prefix: `${this.prefix}:${moduleName}:` },
...this.options,
});
}
}
export default new Logger();

View File

@@ -0,0 +1,16 @@
export default {
processors: {},
addPostProcessor(module) {
this.processors[module.name] = module;
},
handle(processors, value, key, options, translator) {
processors.forEach(processor => {
if (this.processors[processor])
value = this.processors[processor].process(value, key, options, translator);
});
return value;
},
};

View File

@@ -0,0 +1,134 @@
// http://lea.verou.me/2016/12/resolve-promises-externally-with-this-one-weird-trick/
export function defer() {
let res;
let rej;
const promise = new Promise((resolve, reject) => {
res = resolve;
rej = reject;
});
promise.resolve = res;
promise.reject = rej;
return promise;
}
export function makeString(object) {
if (object == null) return '';
/* eslint prefer-template: 0 */
return '' + object;
}
export function copy(a, s, t) {
a.forEach(m => {
if (s[m]) t[m] = s[m];
});
}
function getLastOfPath(object, path, Empty) {
function cleanKey(key) {
return key && key.indexOf('###') > -1 ? key.replace(/###/g, '.') : key;
}
function canNotTraverseDeeper() {
return !object || typeof object === 'string';
}
const stack = typeof path !== 'string' ? [].concat(path) : path.split('.');
while (stack.length > 1) {
if (canNotTraverseDeeper()) return {};
const key = cleanKey(stack.shift());
if (!object[key] && Empty) object[key] = new Empty();
object = object[key];
}
if (canNotTraverseDeeper()) return {};
return {
obj: object,
k: cleanKey(stack.shift()),
};
}
export function setPath(object, path, newValue) {
const { obj, k } = getLastOfPath(object, path, Object);
obj[k] = newValue;
}
export function pushPath(object, path, newValue, concat) {
const { obj, k } = getLastOfPath(object, path, Object);
obj[k] = obj[k] || [];
if (concat) obj[k] = obj[k].concat(newValue);
if (!concat) obj[k].push(newValue);
}
export function getPath(object, path) {
const { obj, k } = getLastOfPath(object, path);
if (!obj) return undefined;
return obj[k];
}
export function getPathWithDefaults(data, defaultData, key) {
const value = getPath(data, key);
if (value !== undefined) {
return value;
}
// Fallback to default values
return getPath(defaultData, key);
}
export function deepExtend(target, source, overwrite) {
/* eslint no-restricted-syntax: 0 */
for (const prop in source) {
if (prop in target) {
// If we reached a leaf string in target or source then replace with source or skip depending on the 'overwrite' switch
if (
typeof target[prop] === 'string' ||
target[prop] instanceof String ||
typeof source[prop] === 'string' ||
source[prop] instanceof String
) {
if (overwrite) target[prop] = source[prop];
} else {
deepExtend(target[prop], source[prop], overwrite);
}
} else {
target[prop] = source[prop];
}
}
return target;
}
export function regexEscape(str) {
/* eslint no-useless-escape: 0 */
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
}
/* eslint-disable */
var _entityMap = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;',
'/': '&#x2F;',
};
/* eslint-enable */
export function escape(data) {
if (typeof data === 'string') {
return data.replace(/[&<>"'\/]/g, s => _entityMap[s]);
}
return data;
}
export const isIE10 =
typeof window !== 'undefined' &&
window.navigator &&
window.navigator.userAgent &&
window.navigator.userAgent.indexOf('MSIE') > -1;

View File

@@ -0,0 +1,19 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"lib": ["es6", "dom"],
"jsx": "react",
"moduleResolution": "node",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noEmit": true,
"baseUrl": ".",
"paths": { "i18next": ["./index.d.ts"] },
"esModuleInterop": true,
"allowSyntheticDefaultImports": true
},
"include": ["./index.d.ts", "./test/**/*.ts*"],
"exclude": ["test/typescript/nonEsModuleInterop/**/*.ts"]
}

View File

@@ -0,0 +1,8 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
// typescript defaults to these
"esModuleInterop": false,
"allowSyntheticDefaultImports": false
}
}

View File

@@ -0,0 +1,7 @@
{
"defaultSeverity": "error",
"extends": "dtslint/dtslint.json",
"rules": {
"semicolon": false
}
}