simply-translate
TypeScript icon, indicating that this package has built-in type declarations

0.20.0 • Public • Published

Simply Translate

Simplest translations for JS. Even consider it more as an object mapper, a Dictionary, but not translation AI or Bot or something... :)
[Typescript support]

Breaking changes

(v0.20.0)

  • added middleware pipeline (see Pipeline).
  • added remainder (modulo) operator %.
  • added ends-with and starts-with operators ....
  • added truthy/falsy operators !/!!.
  • added cases functionality (see Cases).
  • added double curly brackets {{...}} support for placeholder.
  • deprecated defaultLang property over lang name.
  • deprecated $less property. Instead of $less use placeholder = 'single'.
  • not falling back to placeholder property name.
  • removed dynamic cache.
  • deprecated fallbackLang property fallbackLang remains.
  • added commonjs version (simply-translate/commonjs)

(v0.10.0)

  • $T{...} replaced with $&{...}.
  • {$} and $T{$} removed from pluralization, use $# instead (see Plural translations).

Install

npm i simply-translate

Import

ES6 modules

import { Translations } from 'simply-translate';

CommonJS modules

import { Translations } from 'simply-translate/commonjs';

Initialize

const dics = {...};
const translations = new Translations(dics, {lang:'en-US'});

Dictionaries

JSON with languge identifier in the root

const dics = {
  "en-US": ...,
  "ru-RU": ...
};

Dictionary entry

is a set of values with a unique string as a key and a string or object with value (which is required), description, and (optionally) plural and cases data.

const dics = {
    'en-US': {
        hello_world: 'Hello World',
        goodbye_world: {
            value: 'Goodbye World',
            description: 'When you want to say goodbye to the world',
        },
    },
};

Translate

translate or translateTo methods.
translate method uses lang property of Translations, translateTo requires language parameter.

// create translations with dictionary:
const translations = new Translations({...}, {lang:'en-US'});
const translated = translations.translate('hello_world');
const translated = translations.translateTo('en-US', 'hello_world');

For Dynamic data use ${...} with field name of the data object.

const dics = {
    'en-US': {
        hello_user: 'Hello ${user}!',
    },
};
const translations = new Translations(dics, { lang: 'en-US' });
translations.translate('hello_user', { user: 'Oleg' });
// Hello Oleg!

v0.0.20 $less is deprecated. Instead of $less use placeholder = 'single'.
It is required to add $ before placeholders. However it is possible to use $-less placeholders by setting placeholder property of Translations to single ({...}) or double ({{...}}) curly-braces, however it is not recommended.

const dics = {
  "en-US": {
    hello_user: "Hello {user}!",
  },
};
const translations = new Translations(dics, { lang: "en-US", placeholder = 'single' });
translations.translate("hello_user", { user: "Oleg" }, "Hello {user}");
// Hello Oleg

// or
translations.placeholder = 'double';
translations.translate("hello_user", { user: "Oleg" }, "Hello {{user}}");
// Hello Oleg

Please note $ prefix will replace placeholder with property value from the data object, $& translate value from data object, and & will just translate the placeholder text.
And note: single or double placeholder ignores $ as if it is there so using just translate & function is not available.

const dics = {
    'en-US': {
        hello_user: 'Hello ${user}!',
        hello_user_r: 'Hello $&{usr}!',
        hello_user_t: 'Hello &{usr}!',
        usr: 'User',
        oleg: 'Олег',
    },
};
const translations = new Translations(dics, { lang: 'en-US' });
translations.translate('hello_user', { user: 'oleg' });
// Hello oleg!
translations.translate('hello_user_t', { user: 'oleg' });
// Hello User!
translations.translate('hello_user_r', { user: 'oleg' });
// Hello Олег!

Namespaces

Group items in dictionary.

const dics = {
    'en-US': {
        user: {
            hello_user: 'Hello ${user}!',
            goodbye_user: { value: 'Goodbye ${user}!' },
        },
    },
};
const translations = new Translations(dics, { lang: 'en-US' });
translations.translate('user.hello_user', { user: 'Oleg' });
// Hello Oleg!
translations.translate(['user', 'goodbye_user'], { user: 'Oleg' });
// Goodbye Oleg!

You don't need to directly point to value, it is done by default.

Do not use namespaces separator (.) for dictionary keys.

Fallback value

If value is not found and fallback is not provided, key will be used as value.

const dics = {
    'en-US': {
        hello_world: 'Hello World',
    },
};
const translations = new Translations(dics, { lang: 'en-US' });
translations.translate('hello_user}', { user: 'Oleg' }, 'Hello ${user}');
// Hello Oleg!

You may use ${...} in keys, however it is not required, but might be useful.

const dics = {
    'en-US': {
        'hello_${user}': 'Hello ${user}!',
    },
};
const translations = new Translations(dics);
translations.translateTo('en-US', 'hello_${user}', { user: 'Oleg' });
// Hello Oleg!
translations.translateTo('en-US', 'goodbye_${user}', { user: 'Oleg' });
// goodbye_Oleg!

It is possible to use fallback values for dynamic fields. Note: Due to implementation limitations (and keep library clean of dependencies) only Latin characters supported for placeholders and fallback.
Since ver.0.20.0 if property is null or undefined placeholder will be empty (not property name as it was).

const dics = {
    'en-US': {
        'hello_${user}': 'Hello ${user?User}!',
    },
};
const translations = new Translations(dics, { lang: 'en-US' });

translations.translate('hello_user', { user: undefined });
// Hello User!
translations.translate('hi_user', { user: undefined }, 'Hi ${user?Friend}');
// Hi Friend!
translations.translate('hi_user', { user: undefined }, 'Hi ${user}');
// Hi !

Next will fail to replace placeholder:

translations.translateTo('ru-RU', 'hi_${user}', { user: 'Олег' }, 'Привет ${user?Пользователь}');
// Привет ${user?Пользователь}

To solve this add translation term:

translations.extendDictionary('ru-RU', {
    User: 'Пользователь',
});
translations.translateTo('ru-RU', 'hi_${user}', { user: undefined }, 'Привет $&{user?User}');
// Привет Пользователь

Fallback language

Fallback language will use dictionary if selected language does not contain translation. Fallback dictionary will be used before fallback value.

const dics = {
    'en-US': {
        'hello_${user}': 'Hello ${user?User}!',
        'goodbye_${user}': 'Goodbye ${user?User}!',
    },
    'ru-RU': {
        'hello_${user}': 'Привет, $&{user}!',
        user: 'Пользовтель',
        Oleg: 'Олег',
    },
};
const translations = new Translations(dics, {
    lang: 'ru-RU',
    fallbackLang: 'en-US',
});

translations.translate('hello_${user}', { user: 'Oleg' });
// Привет, Олег!
translations.translate('goodbye_${user}', { user: 'Oleg' }, 'Bye ${user?User}');
// Goodbye Олег!
translations.translate('nice_day_${user}', { user: undefined }, 'Have a nice day ${user?Friend}');
// Have a nice day Friend

Pluralization

Use $# in plural options to insert number.
plural property of translation value used for pluralization. Execution order is sequential.
The structure of pluralization entry is a tuple: [operation, value].
Supported operators: truthy/falsy !!/!, compare: >,<,=,<=,>=; and few more: in [] between, %, ..., and _ for default. Operations can only be done with static numbers provided in operation.
Please note: remainder operator % compares remainder (or modulo) operation result with 0. It is possible to use % with specific remainder: %2=0.

let translations = new Translations(
    {
        'en-US': {
            'i-ate-eggs-bananas-dinner': {
                value: 'I ate ${bananas} and ${eggs} for dinner',
                plural: {
                    bananas: [
                        ['<= 0', 'no bananas'],
                        ['...2', 'number of bananas that ends with 2'],
                        ['= 1', 'one banana'],
                        ['in [3,4]', 'few bananas'],
                        ['% 11', 'many bananas that is divisible by eleven'],
                        ['> 10', 'too many bananas'],
                        ['>= 5', 'many bananas'],
                    ],
                    eggs: [
                        ['= 0', 'zero eggs'],
                        ['= 1', 'one egg'],
                        ['between 2 and 4', 'some eggs'],
                        ['_', '$# eggs'],
                    ],
                },
            },
        },
    },
    {
        lang: 'en-US',
    }
);

translations.translate('i-ate-eggs-bananas-dinner', {
    bananas: 0,
    eggs: 1,
});
// I ate no bananas and one egg for dinner

translations.translate('i-ate-eggs-bananas-dinner', {
    bananas: 3,
    eggs: 5,
});
// I ate few bananas and 5 eggs for dinner

translations.translate('i-ate-eggs-bananas-dinner', {
    bananas: 1,
    eggs: 1,
});
// I ate one banana and one egg for dinner

translations.translate('i-ate-eggs-bananas-dinner', {
    bananas: 13,
    eggs: 0,
});
// I ate too many bananas and zero eggs for dinner

translations.translate('i-ate-eggs-bananas-dinner', {
    bananas: 121,
    eggs: 3,
});
// I ate many bananas that is divisible by eleven and some eggs for dinner

translations.translate('i-ate-eggs-bananas-dinner', {
    bananas: 6,
    eggs: 3,
});
// I ate many bananas and some eggs for dinner

translations.translate('i-ate-eggs-bananas-dinner', {
    bananas: 12,
    eggs: one,
});
// I ate number of bananas that ends with 2 and one eggs for dinner

Plural translations

In case if dynamic parameters have to be translated you can use $&{$#} syntax.
It is possible to modify plural translations a little bit like so: $&{my-$#-value}.
In rare cases you are able to use dynamic replacement ${...} placeholders as well.

let translations = new Translations(
    {
        'en-US': {
            'i-ate-apples-for': {
                value: 'I ate ${apples} for $&{when}',
                plural: {
                    apples: [
                        ['= 1', '&{$#-only} apple'],
                        ['in [2,3]', '&{$#} apples'],
                        ['= 5', '$# ($&{yay}) apples'],
                        ['_', '$# apple(s)'],
                    ],
                },
            },
            dinner: 'Dinner',
            breakfast: 'Breakfast',
            '1-only': 'Only One',
            1: 'One',
            2: 'Two',
            3: 'Three',
            wow: 'WOW!',
        },
    },
    {
        lang: 'en-US',
    }
);
translations.translate('i-ate-apples-for', {
    apples: 1,
    when: 'dinner',
});
// I ate Only One apple for Dinner
translations.translate('i-ate-apples-for', {
    apples: 2,
    when: 'breakfast',
});
// I ate Two apples for Breakfast
translations.translate('i-ate-apples-for', {
    apples: 4,
    when: 'breakfast',
});
// I ate Two apples for Breakfast
translations.translate('i-ate-apples-for', {
    apples: 5,
    when: 'breakfast',
    yay: 'wow',
});
// I ate 5 (WOW!) apples for Breakfast

Cases

(v0.20.0+)
(experimental) Similar to pluralization and executes before pluralization. Supports a bit less operators: truthy/falsy, compare and end/startsWith ... operators.
Little bit different syntax placeholder, similar to translations but instead of & use !: $!{...}.
Use replace pattern $# in combination with $ or & and pluralization.

let translations = new Translations({
    'en-US': {
        somebody_ate_bananas: {
            value: '$!{prefix}${person} ate bananas',
            cases: {
                prefix: [
                    ['!!', '&{$#} '],
                    ['!', ''],
                ],
            },
        },
        sir: 'Sir',
        madam: 'Madam',
    },
});

translations.translate('somebody_ate_bananas', {
    prefix: 'sir',
    person: 'Holmes',
});
// Sir Holmes ate bananas

translations.translate('somebody_ate_bananas', {
    person: 'Holmes',
});
// Holmes ate bananas
let translations = new Translations({
    'en-US': {
        i_have_been_here_count: {
            value: '$!{count} ${days}',
            cases: {
                count: [
                    ['== 0', 'I have not been here'],
                    ['_', "I've been here ${count}"],
                ],
            },
            plural: {
                count: [
                    ['=1', 'once'],
                    ['=2', 'twice'],
                    ['in [3,4,5]', 'few times'],
                    ['>10', 'many times'],
                    ['_', '$# times'],
                ],
                days: [
                    ['<2', 'today'],
                    ['<5', 'for last few days'],
                    ['_', 'for long time'],
                ],
            },
        },
    },
});

translations.translate('i_have_been_here_count', {
    count: 0,
    days: 1,
});
// I have not been here today

translations.translate('i_have_been_here_count', {
    count: 2,
    days: 3,
});
// I've been here twice for last few days

As you can see this is pretty simple but may bring some value for conditional placeholders. Still figuring value of this out...

Operators

  • Truthy/Falsy !!/!: ['!!','appear if value is truthy'] / ['!','appear if value is falsy']
  • Compare >,<,=,<=,>=. Just regular compare operators: ['<2','appear if value is less then 2']
  • In in []: ['in [2,4,8]', 'only for 2, 4, and 8']. Works only for pluralization and with digits.
  • Between between: ['between 2 and 5', 'for 2, 3, 4, and 8']. Works only for pluralization and with digits.
  • Remainder %: ['%2', 'remainder that equals to 0 left over when divided by 2'], ['%3=5', 'remainder that equals to 5 when divided by 3']
  • Ends/Starts with ... operators: ['...2', 'ends with 2'], ['2...', 'starts with 2']
  • and _ for default. Operators uses static values provided in operation.
    Operators in plural or cases executes in the order in which it is listed, so it is important to next rule is not prevented by current, especially default _.

Add terms to dictionary

To extend dictionary with new values use extendDictionary method.

translations.extendDictionary('en-US', {
    fruits: {
        'i-ate-mango': {
            value: 'I ate ${mango}',
            plural: {
                apples: [
                    ['< 1', 'no mangos'],
                    ['= 1', 'one mango'],
                    ['_', '$# mangos'],
                ],
            },
        },
        mango: 'mango',
    },
    tools: {
        fork: 'fork',
    },
});

Pipeline

(v0.20.0+)
(experimental) To manage translation flow now there is a pipeline functionality that runs middlewares.
Default flow is the same, but now it is possible to add custom middlewares to the flow or build custom one from the scratch. At the moment there is SimpleDefaultPipeline which is the old one with fallback language which will be removed in future. And SimplePipeline with access to middlewares collection.
In the Middleware you have access to execution Context. result property contains value that is going to be finial result of the entire flow. And params is the accepted data to be used in the flow. It is intended to be readonly.

const pipeline = new SimplePipeline();
pipeline.addMiddleware((context) => {
        const { params, result } = context;
        if (result.fallingBack) {
            // do some logic here for NOT translated values
            console.warn(`the value for ${params.key} is not translated`);
            result.value = `!WARNING: ${result.value} [${params.key}]`;
        } else {
            // do some logic here for translated values
            result.value = `${result.value}: YAY!`;
        }
    });
let translations = new Translations(..., pipeline);

addMiddleware adds middleware to the end of the pipeline queue. addMiddlewareAt and removeMiddlewareAt adds middleware at the index.

Package Sidebar

Install

npm i simply-translate

Weekly Downloads

65

Version

0.20.0

License

MIT

Unpacked Size

108 kB

Total Files

90

Last publish

Collaborators

  • oleg.wx