Această postare va fi folosită react-intl să te ajute să pleci de la create-react-app la configurarea cadrului pentru o aplicație web finalizată, tradusă!

Am confirmat codul în timp ce am scris această postare, astfel încât veți putea să vă uitați la istoricul meu de confirmări pentru a vedea cu ușurință cum a evoluat codul meu de la început până la sfârșit.

Cum sa configurati internationalizarea in React de la inceput pana
Fotografie de Artem Bali pe Unsplash

Ce este internaționalizarea?

Având în vedere că ați decis să faceți clic pe linkul către această postare, este posibil să aveți cel puțin o idee despre ce este internaționalizarea (i18n). Luat chiar din Site-ul web W3:

„Internaționalizarea este proiectarea și dezvoltarea unui conținut de produs, aplicație sau document care permite localizare ușoară pentru publicul țintă care variază în funcție de cultură, regiune sau limbă. ”

În calitate de dezvoltator, doriți ca conținutul dvs. să fie ușor de citit și utilizat de tot felul de oameni din întreaga lume. Cred că toată lumea este de acord cu asta. Dar știu la ce te gândești:

„Dezvoltarea unei aplicații web pentru oamenii din propria cultură / regiune / limbă este deja destul de dificilă! Nu am timp sau efort pentru i18n! ”

Înțeleg că ai deja limbajul jos. Sperăm că această postare vă va ajuta să vă dați seama că configurarea i18n pentru proiectul dvs. nu este atât de dificilă sau de consumatoare de timp pe cât pare.

Ce face și ce nu reacționează-intl

Dacă sunteți nou în i18n, s-ar putea să aveți câteva gânduri despre ceea ce credeți că o bibliotecă, cum ar fi react-intl ar trebui și nu ar trebui să poată face.

Face:

  • Vă ajută să agregați tot conținutul împrăștiat, astfel încât să poată fi tradus cu ușurință ulterior
  • Vă ajută să faceți față traducerii textului în plus față de date, numere și așa mai departe
  • Oferiți o modalitate ușoară de a importa traducerile în aplicația dvs.

Aceasta nu:

  • Traduceți-vă conținutul
  • Vă spun cum să aflați ce localizare dorește utilizatorul
  • Remediați acea eroare fără legătură cu care v-ați confruntat în ultimele câteva ore (rău, nu?)

Ok, așa că hai să ne îndreptăm!

Configurarea proiectului de exemplu

$ npx create-react-app i18n-example

Voi adăuga react router pentru a arăta cum react-intl funcționează cu mai multe pagini.

$ cd i18n-example && npm install react-router-dom

Exemplul meu de aplicație va avea trei componente React: o pagină principală, o subpagină și o componentă care este importată în subpagină. Vedeți structura fișierului și paginile de mai jos:

/src
  /components
    Weather.js
  /pages
    Home.js
    Day.js
1611199927 546 Cum sa configurati internationalizarea in React de la inceput pana

Starea proiectului până în acest moment poate fi găsită aici.

Configurare react-intl

Acum începe distracția. Vom instala react-intl și trece la treabă!

$ npm install react-intl

Scopul principal din spate react-intl este să permiteți asistență pentru i18n minimizând în același timp impactul asupra fluxului normal de codare. Cu siguranță, aveți conținut în multe locuri din toată aplicația dvs. web. Aveți text, numere și date în paragrafe, tabele și anteturi.

Ce ați face dacă ar trebui să construiți o bibliotecă i18n? Ei bine, aveți acești biți de conținut pe toată aplicația dvs. web. Și doriți ca totul să fie tradus cu ușurință. Dacă ar fi să dai conținutul tău unui traducător, nu le-ai da codul tău și ai spune „noroc, trece la treabă”.

Ați dori să găsiți o modalitate de a vă pune tot conținutul într-un singur fișier și apoi să le dați acel fișier. L-ar traduce în altă limbă, să spună din engleză în spaniolă și ți-ar oferi un fișier cu tot conținutul spaniol.

OK, super. Așa că ați făcut asta, dar acum trebuie să luați conținutul spaniol în acel fișier și să îl distribuiți din nou în locația inițială. Cum ați face asta programatic? Poate că ați atribui ID-uri fiecărui bit de conținut, astfel încât să nu pierdeți locația inițială a fiecărui bit de conținut.

Și cam atât!

Primul pas este să vă înfășurați aplicația în <IntlProvider> componentă:

<IntlProvider>
  <App />
</IntlProvider>

Acum, trebuie să identificați conținutul pentru react-intl care în cele din urmă va fi tradus. Pe pagina principală a aplicației mele, am următorul paragraf:

<p>It is a beautiful day outside.</p>

Trebuie să spun react-intl că acesta este conținut pe care vreau să îl traduc și să-i dau un id, astfel încât să poată urmări acest conținut și locația sa originală:

<FormattedMessage
  id="Home.dayMessage"
  defaultMessage="It's a beautiful day outside."
/>

În mod implicit, textul va fi trimis într-un <span>, deci va trebui să înfășurăm acest lucru în original <p> dacă vrem să rămână un paragraf.

<p>
  <FormattedMessage
    id="Home.dayMessage"
    defaultMessage="It's a beautiful day outside."
  />
</p>

Acum voi face acest lucru pentru tot conținutul din aplicația mea web.

Starea proiectului până acum poate fi găsită aici.

Adăugarea babel-plugin-react-intl

Acum că avem totul configurat, s-ar putea să vă întrebați cum putem agrega cu ușurință tot conținutul respectiv într-un singur fișier. Cu toate acestea, în scopuri de depanare, ar putea fi util să aveți fișiere JSON individuale pentru fiecare componentă React. Ghici ce, există un plugin babel pentru asta!

$ npm install babel-plugin-react-intl

Acest plugin va face o copie a fișierului dvs. src director, dar în loc să aibă fișierele componente React, acesta va avea fișiere json cu conținutul mesajului și id. Unul pentru fiecare fișier component din fișierul src director. Acesta va face acest lucru atunci când alergați npm run build .

Acum trebuie să scoatem din create-react-app, astfel încât să putem adăuga noul nostru plugin în configurația noastră babel. Asigurați-vă că comiteți orice modificare și apoi executați:

$ npm run eject

Acum, va trebui să adăugăm un .babelrc în rădăcina proiectului nostru cu următorul conținut:

{
  "presets":["react-app"],
  "plugins": [
    ["react-intl", {
      "messagesDir": "./public/messages/"
    }]
  ]
}

Acum că babel poate folosi noul nostru plugin extraordinar pe care tocmai l-am adăugat, putem trece la următorul nostru pas: generarea acelor fișiere JSON.

$ npm run build

Odată ce executați acest lucru, ar trebui să observați că aveți un public/messages/src director care pare a fi o clonă a originalului dvs. src director, cu excepția tuturor fișierelor componente sunt de fapt fișiere JSON.

/messages
  /src
    /components
      Weather.json
    /pages
      Home.json
      Day.json

Acum, să vedem conținutul unuia dintre ele, Home.json:

[
  {
    "id": "Home.header",
    "defaultMessage": "Hello, world!"
  },
  {
    "id": "Home.dayMessage",
    "defaultMessage": "It's a beautiful day outside."
  },
  {
    "id": "Home.dayLink",
    "defaultMessage": "Click here to find out why!"
  }
]

Starea proiectului până acum poate fi găsită aici.

Combinarea fișierelor JSON

A făcut exact ceea ce credeam că va fi. Poate fi util să avem conținutul organizat în această structură, dar în cele din urmă vom dori să fie într-un singur fișier și avem nevoie să includă orice traduceri pe care le vom face.

Acum trebuie să realizăm un script care să facă acest lucru pentru noi. Din fericire, cei de la react-intl ne-a oferit un bun punct de plecare cu acest script.

import * as fs from "fs";
import { sync as globSync } from "glob";
import { sync as mkdirpSync } from "mkdirp";
import last from "lodash/last";

const MESSAGES_PATTERN = "./public/messages/**/*.json";
const LANG_DIR = "./public/locales/";
const LANG_PATTERN = "./public/locales/*.json";

// Try to delete current json files from public/locales
try {
  fs.unlinkSync("./public/locales/data.json");
} catch (error) {
  console.log(error);
}

// Merge translated json files (es.json, fr.json, etc) into one object
// so that they can be merged with the eggregated "en" object below

const mergedTranslations = globSync(LANG_PATTERN)
  .map(filename => {
    const locale = last(filename.split("/")).split(".json")[0];
    return { [locale]: JSON.parse(fs.readFileSync(filename, "utf8")) };
  })
  .reduce((acc, localeObj) => {
    return { ...acc, ...localeObj };
  }, {});

// Aggregates the default messages that were extracted from the example app's
// React components via the React Intl Babel plugin. An error will be thrown if
// there are messages in different components that use the same `id`. The result
// is a flat collection of `id: message` pairs for the app's default locale.

const defaultMessages = globSync(MESSAGES_PATTERN)
  .map(filename => fs.readFileSync(filename, "utf8"))
  .map(file => JSON.parse(file))
  .reduce((collection, descriptors) => {
    descriptors.forEach(({ id, defaultMessage }) => {
      if (collection.hasOwnProperty(id)) {
        throw new Error(`Duplicate message id: ${id}`);
      }
      collection[id] = defaultMessage;
    });

    return collection;
  }, {});

// Create a new directory that we want to write the aggregate messages to
mkdirpSync(LANG_DIR);

// Merge aggregated default messages with the translated json files and
// write the messages to this directory
fs.writeFileSync(
  `${LANG_DIR}data.json`,
  JSON.stringify({ en: defaultMessages, ...mergedTranslations }, null, 2)
);

Va trebui să îl modificăm puțin, deoarece, așa cum este, acel script va genera o traducere falsă. Nu vrem acest lucru pentru că nu este practic.

Suntem mai buni de atât! Vrem să citească o traducere reală!

Scriptul pe care îl vom folosi pentru a face acest lucru este mai jos:

Va trebui să salvăm acest fișier în scripts director și apoi editați package.json astfel încât să ruleze de fapt scriptul.

Înainte de a face acest lucru, va trebui să facem câteva lucruri, astfel încât codul nostru ESNext să poată fi înțeles. Mai întâi va trebui să adăugăm babel-cli pentru a vă asigura că scriptul este transpus.

$ npm install --save-dev babel-cli

Apoi, trebuie să adăugăm env presetată la a noastră .babelrc astfel încât să arate astfel:

{
  "presets":["react-app", "env"],
  "plugins": [
    ["react-intl", {
      "messagesDir": "./public/messages/"
    }]
  ]
}

În cele din urmă, trebuie să le edităm package.json astfel încât să ruleze scriptul nostru:

{...
  "scripts": {
    "build:langs": "NODE_ENV='production' babel-node
      scripts/mergeMessages.js",
    "build": "npm run build:langs && node scripts/build.js",
    ...
  },
  ...
}

Rețineți că rulăm scriptul mergeMessages înainte npm run build . Acest lucru se datorează faptului că dorim să generăm finalul nostru data.json fișier în /public înainte ca scriptul nostru de compilare să-l copieze în /build .

Bine, acum când alergăm npm run build ar trebui să vedem build/locales/data.json care combină toate fișierele noastre JSON într-unul.

Starea proiectului până acum poate fi găsită aici.

E timpul să începi traducerea

Acum că am creat un script care va agrega mesajele noastre implicite și traducerile noastre într-un singur fișier, haideți să facem câteva traduceri! Pentru acest exemplu, vom traduce în spaniolă. Scriptul nostru pe care tocmai l-am creat va citi totul *.json fișiere din /public/locales deci va trebui să denumim noul nostru fișier de traducere /public/locales/es.json și adăugați conținutul de mai jos:

{
  "Weather.message": "¡Porque es soleado!",
  "Day.homeLink": "Regresar a inicio",
  "Home.header": "¡Hola Mundo!",
  "Home.dayMessage": "Es un hermoso día afuera.",
  "Home.dayLink": "¡Haz clic aquí para averiguar por qué!"
}

Acum, când alergăm npm run build, scriptul nostru mergeMessages va crea un fișier data.json înregistrați în /public/locales , și apoi va fi copiat în /build/locales. Finalul nostru data.json fișierul va arăta astfel:

{
  "en": {
    "Weather.message": "Because it is sunny!",
    "Day.homeLink": "Go back home",
    "Home.header": "Hello, world!",
    "Home.dayMessage": "It's a beautiful day outside.",
    "Home.dayLink": "Click here to find out why!"
  },
  "es": {
    "Weather.message": "¡Porque es soleado!",
    "Day.homeLink": "Regresar a inicio",
    "Home.header": "¡Hola Mundo!",
    "Home.dayMessage": "Es un hermoso día afuera.",
    "Home.dayLink": "¡Haz clic aquí para averiguar por qué!"
  }
}

Suntem aproape acolo! Ultimul pas este încărcarea dinamică a versiunii în limba spaniolă a textului dacă setările browserului utilizatorului sunt spaniole. Trebuie să edităm index.js pentru a citi setările de limbă a browserului și apoi pentru a oferi aceste informații împreună cu traducerile corecte către <IntlProvider /> și în cele din urmă aplicația noastră.

Finalul nostru index.js arata asa:

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import registerServiceWorker from "./registerServiceWorker";
import { BrowserRouter } from "react-router-dom";
import { IntlProvider, addLocaleData } from "react-intl";
import en from "react-intl/locale-data/en";
import es from "react-intl/locale-data/es";

import localeData from "./../build/locales/data.json";

addLocaleData([...en, ...es]);

// Define user's language. Different browsers have the user locale defined
// on different fields on the `navigator` object, so we make sure to account
// for these different by checking all of them
const language =
  (navigator.languages && navigator.languages[0]) ||
  navigator.language ||
  navigator.userLanguage;

// Split locales with a region code
const languageWithoutRegionCode = language.toLowerCase().split(/[_-]+/)[0];

// Try full locale, try locale without region code, fallback to 'en'
const messages =
  localeData[languageWithoutRegionCode] ||
  localeData[language] ||
  localeData.en;

ReactDOM.render(
  <IntlProvider locale={language} messages={messages}>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </IntlProvider>,
  document.getElementById("root")
);
registerServiceWorker();

(Cod foarte copiat din esența lui Preethi Kasireddy aici)

Un alt lucru mic pe care trebuie să-l facem este să edităm configurațiile noastre webpack pentru a permite importurile în afara src și node_modules .

Acum, dacă schimbăm setările browserului în spaniolă, ar trebui să vedem conținutul nostru tradus în spaniolă!

1611199928 177 Cum sa configurati internationalizarea in React de la inceput pana

Starea finală a proiectului poate fi găsită aici.