npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

easy-typed-intl

v1.0.3

Published

Модуль для работы с i18n

Downloads

1,160

Readme

I18N

Интерфейс для локализации текста с динамическими параметрами.

Установка

$ npm i easy-typed-intl

Использование

Определите модуль, который экспортирует функцию для определения текущей локали, например в src/i18n/getLang.ts

type Lang = 'ru' | 'en';

export default function getLang(): Lang {
    const maybeLang = navigator.language.split(/-|_/)[0];
    if (maybeLang) {
        return maybeLang as Lang;
    }

    return 'ru';
}

Если вы используете --split (см. Опции генератора), то данную функцию можно определить проще:

type Lang = 'ru' | 'en';

export default function getLang(): Lang {
    return process.env.REACT_APP_LANG as Lang;
}

В коде приложения оберните строку, для которой требуется перевод, в вызов функции t или t.raw (вы можете задать любое имя с помощью параметра -f, --func):

Было

React.useEffect(() => {
    alert('Очень удобное уведомление');
}, []);

return (
    <div>
        <span>Привет</span>
        <button type="button">
            Нажмите <Icon /> кнопку
        </button>
    </div>
);

Стало:

React.useEffect(() => {
    alert(t('Очень удобное уведомление'));
}, []);

return (
    <div>
        <span>{t('Привет')}</span>
        <button type="button">{(t.raw('Нажмите {btn} кнопку'), { btn: <Icon key="somekey" /> })}</button>
    </div>
);

После чего запустите генератор командой

generate-i18n --langs=ru,en --path='src/components/**/*' --getLang='./src/i18n/getLang'

Результатом будет новый каталог, который будет помещён рядом с изменённым файлом и будет иметь примерно следующую структуру (зависит от используемых языков):

src/components/SomeComponent
├── SomeComponent.tsx
├── SomeComponent.i18n — файлы переводов
│   ├── ru.json — словарь для русского языка
│   ├── en.json — словарь для английского языка
│   └── index.ts — модуль, где хранится функция "t"
// src/components/SomeComponent/SomeComponent.i18n/ru.json
{
  "Очень удобное уведомление": "",
  "Привет": ""
}

// src/components/SomeComponent/SomeComponent.i18n/en.json
{
  "Очень удобное уведомление": "",
  "Привет": ""
}

Далее, необходимо в код явно импортировать функцию t из только что созданного файла src/components/SomeComponent/SomeComponent.i18n/index.ts

import { t } from './SomeComponent.i18n';
...
React.useEffect(() => {
    alert(t('Очень удобное уведомление'));
}, []);

return (
    <div>
        <span>{t('Привет')}</span>
        <button type="button">
            {t.raw('Нажмите {btn} кнопку'), {
                btn: <Icon key="somekey" />
            }}
        </button>
    </div>
);

Для сохранения контекста и передачи комментария переводчику, можно сразу после строки с ключом написать комментарии для дальнейшей выгрузки:

return <span>{t('Привет' /* Приветствие при включении устройства */)}</span>;

или

return <span>{t(/* Приветствие при включении устройства */ 'Привет')}</span>;

Опции генератора

  • -l, --langs – список языков через запятую, для перевода, включая базовый язык. Пример: --langs=ru,en,es,jp
  • --getLang – путь до модуля, экспортирующего по-умолчанию функцию для определения текущего языка
  • -f, --func – имя функции, которая будет вызываться в вашем проекте по назначению. По-умолчанию равно t.
  • -s, --split – разделить импорт файлов локализации с учётом значения env. Используйте этот флаг, если хотите для каждой локали сделать отдельную сборку
  • -e, --env – имя переменной окружения, в которой содержится текущая локаль. Используется только если передан парамтер -s. По-умолчанию REACT_APP_LANG
  • --fmt – путь до модуля форматирования. См. Использование своего собственного модуля форматирования
  • --sort – сортировать ключи в алфавитном порядке
  • -p, --path – список путей, в которых парсер будет искать вызов функции func

Описание

Генератор json файлов использует @babel/core для получения строк. Существует три определяющих фактора, от которых напрямую зависит результат работы матчера:

  1. вызов t (настраивается параметром -f, --func)
  2. вызов t.raw (настраивается параметром -f, --func)
  3. первый аргумент функции – это синтаксическая строка

Т.е. написать что-то вроде

const text = 'Привет';
<span>{t(text)}</span>;

нельзя, т.к. первый аргумент синтаксически является переменной, также как и

<span>{t('Привет' + '!')}</span>

где первый аргумент является выражением сложения, а не синтаксической строкой.

Функция t всегда возвращает строку.

Функция t.raw всегда возвращает массив, который содержит значения string | number | null | T, где T – generic-параметр функции, который будет выведен при использовании.

Добавление команды в package.json

В секцию scripts, добавьте команду

"generate-i18n": "generate-i18n --langs=ru,en --path='./src/components/**/*' --getLang='./src/i18n/getLang'"

или более сложный вариант

"generate-i18n": "generate-i18n --split --langs=ru,en --fmt=./src/i18n --path='src/{components,containers,pages,utils,forms}/**/*' --getLang='./src/i18n/getLang'"

На данный момент функция t автоматически не импортируется, поэтому необходимо самостоятельно добавить импорт

import { t } from './ComponentName.i18n';

С использованием codeshift/recast можно сделать инъекцию импорта.

Создание словарей переводов

Файлы с переводами лежат рядом с кодом, к которому они логически относятся. Файл словаря — модуль, в котором лежит кейсет для языка и функция t. По-умолчанию, если значение для ключа не задано, то функция t вернёт сам ключ. Если по какой-то причине вам все-таки требуется оставить поле пустым, вы можете добавить null в качестве значения.

Entity/Entity.i18n/en.json

{
    "Пока": "Bye",
    "Привет": "Example",
    "Поле только в ru локали": null
}

Entity/Entity.i18n/ru.json

{
    "Пока": "",
    "Привет": "",
    "Поле только в ru локали": "Привет"
}

Форматирование

По-умолчанию i18n умеет только подставлять параметры. Например

function buy(apples: number, plums: number) {
    alert(
        t('Купи {apples}кг яблок и {plums}кг слив', {
            apples,
            plums,
        }),
    );
}

function buyRaw(apples: number, plums: number) {
    console.log(
        t.raw('Купи {apples}кг яблок и {plums}кг слив', {
            apples,
            plums,
        }),
    );
}

buy(3, 2); // alert('Купи 3кг яблок и 2кг слив')
buyRaw(3, 2); // console.log(['Купи ', 3, 'кг яблок и ', 2, 'кг слив'])

Метод t.raw может быть полезен, если вы хотите в форматируемую строку вставить компонент

Использование своего собственного модуля форматирования

Если в проекте необходимо использовать более сложное форматирование, то с помощью параметра --fmt можно указать путь до своей собственной имплементации. Пример ниже добавляет поддержку синтаксиса ICU в функцию форматирования строки и используется форматирование по-умолчанию для массивов:

src/i18n/formatter.ts

import { IntlMessageFormat } from 'intl-messageformat';
import { I18nFormatter, I18nFormatterStr, formatRaw } from 'easy-typed-intl';

// модуль, определённый ранее для параметра --getLang
import getLang from './i18n/getLang';

const formatStr: I18nFormatterStr = (str, opts) => {
    const intl = new IntlMessageFormat(str, getLang());
    return intl.format(opts) as string;
};

const fmt: I18nFormatter = {
    str: formatStr,
    raw: formatRaw,
};

export default fmt;

package.json

"generate-i18n": "generate-i18n --langs=ru,en --path='./src/components/**/*' --getLang='./src/i18n/getLang' --fmt=./src/i18n/formatter"

src/components/SomeComponent.tsx

<span>
    {t(
        '{count, plural, one {# яблоко стоит} few {# яблока стоят} other {# яблок стоят}} {price, number, :: scale/0.01 . currency/RUB}',
        {
            count: props.applesCount,
            price: props.applePrice,
        },
    )}
</span>

Пример можно упростить если перенести форматирование из ключа в значение:

src/components/SomeComponent.tsx

<span>
    {t('{count} {price}', {
        count: props.applesCount,
        price: props.applePrice,
    })}
</span>

src/components/SomeComponent.i18n/ru.json

{
    "{count} {price}": "{count, plural, one {# яблоко стоит} few {# яблока стоят} other {# яблок стоят}} {price, number, :: scale/0.01 . currency/RUB}"
}

Выгрузка

Для выгрузки переводов с мета информацией, добавьте команду в package.json:

    "extract-i18n-meta": "extract-i18n-meta --langs=ru,en --path='./src/{components,containers,pages,utils,forms}/**/*'"

И запускайте npm run extract-i18n-meta -- --out i18n-export.json, что выведет в файл i18n-export.json JSON со всеми существующими переводами и комментариями, сгруппированным по скоупам:

$ npm run extract-i18n-meta

{
    "meta": {
        "meta": {
        "version": "1.3.0",
        "commit": "eca85282c2c40570c624b6e37c888fa8d3ac4675",
        "datetime": "Mon May 24 2021 09:19:42 GMT+0300 (GMT+03:00)",
        "timestamp": 1621837182414,
        "pathPrefix": "packages/awesome-package/",
        "repo": "[email protected]:easy-typed-intl.git"
    },
    "entries": {
        "src/components/ProductControls": {
            "Добавить": {
                "comment": "Кнопка добавления услуги",
                "translations": {
                    "en": "Add",
                    "ru": "Добавить"
                }
            }
        }
    }
}

Импорт переводов

Для импорта переводов из файла, добавьте команду в package.json:

    "import-i18n": "import-i18n --langs=ru,en --path='src/{components,containers,pages,utils,forms,hooks}/**/*'"

Запускайте с дополнительным параметром, укзывая имя файла с данными npm run import-i18n -- --input i18n-export.json.

После работы скрипта обновятся json файлы с языками, в случае наличия лишних ключей, предупреждение будет выведено в консоль.

CSV

Для импорта и экспорта добавить в package.json:

    "export-i18n-csv": "export-i18n-csv --lang=en --path='src/{components,containers,pages,utils,forms,hooks}/**/*'",
    "import-i18n-csv": "import-i18n-csv --lang=en --path='src/{components,containers,pages,utils,forms,hooks}/**/*'"

и запускать

npm run export-i18n-csv -- --out=b2b-translations.csv

В полученом файле формат

Привет, Надпись на странице онбординга, Hello
"Добро
Пожаловать", Пример многострочной строки, "Wel-
come"

Комментарий изменять нельзя, он также используется для идентификации перевода, т.к. одна фраза может быть в разных местах, но переводиться на английский по-разному.

Импорт

npm run import-i18n -- --input=b2b-translations.csv