8000 GitHub - deriv-com/translations: Deriv Translations NPM package · GitHub
[go: up one dir, main page]

Skip to content

About

Deriv Translations NPM package

Resources

Stars

0 stars

Watchers

1 watching

Forks

Repository files navigation

@deriv-com/translations

This is a localization library that uses i18next, react-i18next, and a custom OTA SDK for translations.

In this document

Overview

image

Requirements

The stored translation directory must have the following structure: {{ BASE_URL }}/translations/{lang}.json

base_url is the cdnUrl passed to the initializeI18n function, and lang is the language code. Refer to the example

Getting Started

To get started, one would need few things to setup first which are:

  1. A Crowdin project which will be used to manage translations for your project.
  2. A Cloudflare R2 account to store the translations.
    • Create a new bucket in the R2 account.
    • Create a new access key in the R2 account.
  3. Add the following secrets to your repository:
    • CROWDIN_PROJECT_ID: Open your crowdin project and navigate to Tools tab, you will be able to get the Project ID, store that in your github secrets.
    • CROWDIN_PERSONAL_TOKEN: This can be obtained by logging in to your crowdin account and clicking on your profile icon and goto Settings > API and create a new personal access token, store that in your github secrets.
    • R2_ACCOUNT_ID: R2 account ID from the Cloudflare R2 dashboard.
    • R2_ACCESS_KEY_ID: R2 access key ID from the Cloudflare R2 dashboard.
    • R2_SECRET_ACCESS_KEY: R2 secret access key from the Cloudflare R2 dashboard.
    • R2_BUCKET_NAME: R2 bucket name from the Cloudflare R2 dashboard.
  4. Add the following environment variable in your repository:
    • PROJECT_NAME: Add project name in your environment variable which will be used for Cloudflare R2 folder name for translations.
    • CROWDIN_BRANCH_NAME: Add the branch name for your crowdin project to separate the translations based on environment, this needs to be put into the environment variable instead of secret because the consumer of the package needs it while defining the cdnUrl to access the translations.
  5. Setup the github action to sync the translations to the CDN, refer to the Syncing translations section for more details.

Install the package by running:

npm install @deriv-com/translations

Setup

  • initialize translations in main component by importing and calling initializeI18n outside of the component function.
  • pass the return value to the TranslationProvider component from @deriv-com/translations.
  • pass default language to the TranslationProvider component.
    import { initializeI18n, TranslationProvider } from '@deriv-com/translations';
    ...
    const i18nInstance = initializeI18n({ cdnUrl: 'https://cdn.example.com' })

    const App = () => {
        ...
        return (
            <TranslationProvider defaultLang={'EN'} i18nInstance={i18nInstance}>
                <App />
            </TranslationProvider>
        )
    }

Usage

initializeI18n

The initializeI18n function initializes the i18next instance with the OTA SDK, react-i18next, and a language detector. It takes an object with a cdnUrl property, which is the URL of the CDN where the translations are stored.

import initializeI18n from "@deriv-com/translations";

initializeI18n({ cdnUrl: "https://cdn.example.com" });
  • For strings use either localize(...) or <Localize />.

Localize component example:

import { Localize } from "@deriv-com/translations";

<Localize
  i18n_default_text="You cannot use your real money account with {{website_name}} at this time."
  values={{ website_name }}
/>;

localize example:

Note that the localize function will not get the update from i18n instance once there is any changes like resource loaded or language change. This localize function is not to be used to wrap strings in components and only suitable for util/mapper functions which would not cause issues with string not getting updated.

import { localize } from "@deriv-com/translations";

const getNotification = () => ({
  all: localize("all notifications"),
});

useTranslations Hook

The useTranslations hook is a custom hook that adds some more returned values on top of the useTranslation hook from react-i18next. It can be used to translate strings in your components, toggle the language of the app and see the current language etc.

Example usage:

import { useTranslations } from "@deriv-com/translations";

const MyComponent = () => {
  const { localize, switchLanguage, currentLang } = useTranslations();

  const handleLanguageChange = () => {
    switchLanguage(currentLang === "EN" ? "DE" : "EN");
  };

  return <p onClick={handleLanguageChange}>{localize("Change language")}</p>;
};

Syncing translations

There is a github action that syncs the translations from Crowdin to the CDN.

The action takes following inputs:

  • PROJECT_SOURCE_DIRECTORY: Source directory of your project by default it is src.
  • CROWDIN_BASE_PATH: Base path of the translations in the Crowdin project by default it is ..
  • CROWDIN_BASE_URL: Base URL of the CDN where the translations are stored, default is https://api.crowdin.com.
  • CROWDIN_BRANCH_NAME: Running on production, test or staging etc.
  • CROWDIN_PROJECT_ID: Crowdin project ID which can be found in the crowdin project settings.
  • CROWDIN_PERSONAL_TOKEN: Crowdin personal token which can be found in the crowdin account settings.
  • R2_ACCOUNT_ID: R2 account ID from the Cloudflare R2 dashboard.
  • R2_ACCESS_KEY_ID: R2 access key ID from the Cloudflare R2 dashboard.
  • R2_SECRET_ACCESS_KEY: R2 secret access key from the Cloudflare R2 dashboard.
  • R2_BUCKET_NAME: R2 bucket name from the Cloudflare R2 dashboard.

Refer to the action file here.

Example usage of the action in the workflow file:

Copy and paste the following workflow code in your repository in this path.github/workflows/sync_translations.yml.

name: Sync translations

on:
  push:
    branches:
      - 'main'
  schedule:
    - cron: '0 */12 * * *'

jobs:
  sync_translations:
    runs-on: ubuntu-latest
    steps:
      - name: Sync translations
        uses: deriv-com/translations/.github/actions/extract_and_sync_translations@master
        with:
          PROJECT_NAME: ${{ env.PROJECT_NAME }}
          CROWDIN_BRANCH_NAME: ${{ env.CROWDIN_BRANCH_NAME }}
          CROWDIN_BASE_URL: ${{ env.CROWDIN_BASE_URL }}
          CROWDIN_BASE_PATH: ${{ env.CROWDIN_BASE_PATH }}
          CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
          CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
          R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
          R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
          R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
          R2_BUCKET_NAME: ${{ secrets.R2_BUCKET_NAME }}

Syncing translations with Makima

909A

Crowdin is being decommissioned in favour of the internal Makima translation gateway. extract_and_sync_translations_makima is a drop-in sibling of the Crowdin action: it runs the same extractor and uploads the same translations/<locale>.json files to the same R2 folder, so the runtime needs no change. Only the middle step differs — new strings are translated by Makima instead of downloaded from Crowdin.

How it keeps translations stable:

  • Delta only. The current <locale>.json is fetched from the CDN first and used as the translation memory. Keys already there are kept verbatim, keys no longer in en.json are pruned, and only missing keys are sent to Makima. Because keys are the crc32 of the English text, a reworded string is a new key.

  • Protected tokens. {{placeholders}}, <0>…</0> component markers and any terms listed in PROTECTED_TERMS_FILE (brand and product names) are swapped for HTML tokens Makima preserves, restored afterwards, and verified to appear exactly once. A string that fails the check is left out of the file — the runtime falls back to English for it and the next run retries it — and is listed in the job summary.

  • Partial results. If Makima fails a language, the locales that succeeded are published, the failed locale's file is left as the CDN had it, and the run is marked failed after the upload so the site keeps working.

  • Dry run. With DRY_RUN: "true" the catalogues are attached as a workflow artifact instead of being uploaded; use it for the first run on each environment and diff the artifact against the live files. Make sure that first run includes a few new strings with &, <, quotes and a {{x}} placeholder, so the gateway's handling of them is seen before anything is published.

  • What counts as translated. Any string value already on the CDN is reused, including one identical to the English. That is deliberate (product names such as "Wallet API" are kept in English on purpose), but it also means a key that was once published untranslated is never resent; delete it from the CDN file to have it translated.

  • Reviewed values travel between environments. With FALLBACK_ENVIRONMENT: staging on the production sync, a key production lacks takes the value in the staging catalogue (the one QA reviewed) before Makima is asked; equal keys mean equal English, because keys are the crc32 of the text. FALLBACK_MODE: prefer goes further: staging's value also replaces a different one production already has, so the two catalogues converge. A staging value must pass the same placeholder, marker and protected-term check as Makima's output; one that does not is listed in the job summary and the key falls through to production's own value (prefer) or Makima (missing). Keys only staging has are never added, and production keys no longer in en.json are still pruned.

  • Corrections go through a pull request. OVERRIDES_FILE points at a JSON file in your repository, keyed by locale and then by the English source text:

    { "fr": { "1 Change": "1 modification" }, "es": { "Edited {{time}} ago": "Editado hace {{time}}" } }

    Overrides win over every other source, in every environment that passes the file. An entry that fails the token check fails the run, even when the run does not target its locale, and so does a language named twice (zh-CN and zh_cn); an entry whose English is no longer extracted (the string was reworded or removed) is skipped and listed in the job summary, so remove or update it.

The action takes the following inputs:

  • PROJECT_NAME, PROJECT_SOURCE_DIRECTORY, R2_ACCOUNT_ID, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_BUCKET_NAME: as for the Crowdin action.
  • ENVIRONMENT: staging or production — the R2 sub-folder (what CROWDIN_BRANCH_NAME was for).
  • TARGET_LOCALES: comma-separated locale codes to translate into, e.g. es,fr,pt. Chinese also accepts the runtime's zh_cn / zh_tw (the ZH_CN / ZH_TW language codes), and the file keeps that name.
  • MAKIMA_API_KEY: Bearer token for the gateway (a secret, minted by the platform team against your team's LiteLLM service-account key).
  • MAKIMA_API_BASE: base URL of the gateway as given by the platform team. Required, with no default, so no consumer silently relies on a dev host.
  • CDN_BASE_URL: where the current catalogues are served (default https://translations.deriv.com).
  • PROTECTED_TERMS_FILE: optional path to a JSON array of strings that must never be translated.
  • FALLBACK_ENVIRONMENT: optional staging or production, another environment whose catalogue is consulted before Makima. Must differ from ENVIRONMENT.
  • FALLBACK_MODE: missing (default) uses the fallback only for keys this environment lacks; prefer also lets it replace this environment's own values.
  • OVERRIDES_FILE: optional path to reviewed corrections, shaped {"<locale>": {"<English text>": "<translation>"}}.
  • DRY_RUN: true to write an artifact instead of uploading.

It exposes four outputs: status (noop when the catalogues are unchanged, completed when new translations, the fallback, overrides or pruning changed them, or partial), kept_back (the number of strings left out by the token check), from_fallback (values taken from FALLBACK_ENVIRONMENT) and overridden (values OVERRIDES_FILE changed or added; an override already published counts as reused).

Pin the action to a release commit by SHA (…_makima@<sha> # v1.x.y) rather than @master. Both actions run their scripts from their own commit, with dependencies from that commit's package-lock.json, and never install @deriv-com/translations from npm. So the pin also covers the extractor and the sync that runs with MAKIMA_API_KEY, not only the action's steps.

Example usage of the Makima action in the workflow file:

name: Sync translations

on:
  push:
    branches:
      - 'main'
  schedule:
    - cron: '0 */12 * * *'

# Two runs reading the same CDN snapshot would both upload, last writer wins;
# serialise them.
concurrency:
  group: sync-translations-makima
  cancel-in-progress: false

jobs:
  sync_translations:
    runs-on: ubuntu-latest
    steps:
      - name: Sync translations
        uses: deriv-com/translations/.github/actions/extract_and_sync_translations_makima@master
        with:
          PROJECT_NAME: ${{ vars.R2_PROJECT_NAME }}
          PROJECT_SOURCE_DIRECTORY: './src'
          ENVIRONMENT: staging
          TARGET_LOCALES: es,fr,pt
          MAKIMA_API_KEY: ${{ secrets.MAKIMA_API_KEY }}
          MAKIMA_API_BASE: ${{ vars.MAKIMA_API_BASE }}
          R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
          R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
          R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
          R2_BUCKET_NAME: ${{ secrets.R2_BUCKET_NAME }}

The same bin can be run locally for a dry run after npx deriv-extract-translations ./src:

PROJECT_NAME=my-project ENVIRONMENT=staging TARGET_LOCALES=es,fr,pt \
MAKIMA_API_BASE=https://… MAKIMA_API_KEY=… DRY_RUN=true npx deriv-makima-sync

Contributing

Contributions are welcome. Please open a pull request with your changes.

About

Deriv Translations NPM package

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Used by

Contributors

Languages

0