i18n за рендирани Angular приложения от страна на сървъра

AngularInDepth се отдалечава от Medium. По-новите статии се хостват на новата платформа inDepth.dev. Благодарим ви, че сте част от дълбокото движение!

🤔 Фонът

Какво означава i18n и защо има „18“ в средата? Дори като инженер с повече от десет години опит в областта, нямах представа, докато не го потърсих. Това е броят на буквите между „i“ и „n“ в думата „интернационализация“. И така, i18n е интернационализация. Доста спретнат. Едно от дефинициите на i18n е:

Проектиране и разработване на продукт, приложение или съдържание на документ, което позволява лесно локализиране за целеви аудитории, които се различават по култура, регион или език.

Следвайки дефиниционната връзка на i18n по-горе, можем да видим, че има множество области на развитие, които i18n засяга. Въпреки това, областта, върху която ще се концентрираме в тази статия, е:

Разделяне на локализиращи се елементи от изходния код или съдържание, така че локализирани алтернативи да могат да бъдат заредени или избрани въз основа на международните предпочитания на потребителя, ако е необходимо.

По същество всичко, което трябва да се показва на различни езици, трябва да бъде отделено от месото на кода, за да се позволи поддръжката му.

В статията ще проучим как да внедрим нашите низове за превод по поддържаем начин, да позволим на приложението да зарежда само необходимите ресурси и да позволим запаметяването на браузъра на избрания език. След това ще активираме изобразяване от страна на сървъра (SSR) и ще разрешим проблеми, възникнали при активирането на SSR в приложението Angular.

Статията е разделена на следните части:

Част 1. Определяне на сцената

Част 2. Добавяне на SSR към приложението

Част 3. Решение 1 — Коригиране чрез предоставяне на отделен I18nModule за сървъра

Част 4. Решение 2 — Осигурете всичко в един модул

Част 5. Подобрете производителността с TransferState

Част 6. Стигнахме ли вече?

В първата част на тази статия ще следваме прости инструкции за настройка на Angular приложение и добавяне на i18n възможности към него. Разработчиците на ниво начинаещи може да поискат да се потопят дълбоко в част 1. По-напредналите разработчици могат да погледнат кода в следващите раздели и да продължат към „Част 2. Добавяне на SSR към приложението“, за да разберат какви пречки ще създаде добавянето на SSR и как да ги решим.

📝Част 1 от 6: Определяне на сцената

За целите на тази статия ще работим с голо приложение Angular, генерирано с AngularCLI. За да следваме статията, ще генерираме приложение с помощта на командата (приемайки, че Angular CLI е инсталиран глобално):

ng new ssr-with-i18n

За целите на примера нека добавим няколко компонента:

ng g c comp-a
ng g c comp-b

Сега ще заменим съдържанието на app.component.html с тези два компонента:

*** Кодът до този момент е достъпен тук.

🗺️ Да добавим i18n

Както при повечето неща в кодирането, има много начини да одерете котка. Първоначално исках да използвам независимата от рамката библиотека i18next с Angular обвивка: angular-i18next. Понастоящем обаче има „нещастно ограничение“ с angular-i18next: той не може да превключва езика в движение, което ме спира.

В тази статия ще използваме популярна библиотека: ngx-translate.

Забележка: Концепциите за организиране на модули и код, описани в тази статия, не се отнасят само за ngx-translate. Едно приложение може да използва новата и лъскава библиотека transloco, която беше пусната на датата на написване на тази статия (15.08.2019 г.). Читателят може дори да се опитва да разреши проблем, който няма нищо свързано с преводи. Следователно тази статия е полезна за всеки, който се опитва да реши проблем, свързан с SSR.

Използването на ngx-translateще ни позволи да съхраняваме нашите низове в отделни JSON файлове (файл за всеки език), където всеки низ ще бъде представен от двойка ключ-стойност. Ключът е идентификатор на низ, а стойността е преводът на низа.

  1. Зависимости при инсталиране

В допълнение към основната библиотека ще инсталираме библиотеката http-loader, която ще позволи зареждане на преводи при поискване.

npm install @ngx-translate/core @ngx-translate/http-loader --save

2. Добавете кода

Указанията за пакета ngx-translate предлагат добавяне на подходящ код директно към AppModule. Мисля обаче, че можем да се справим по-добре. Нека създадем отделен модул, който ще капсулира свързаната с i18n логика.

ng g m i18n --module app

Това ще добави нов файл: /i18n/i18n.module.ts и ще го посочи в app.module.ts.

Променете файла i18n.module.ts според документацията. Пълният файл с код е по-долу:

Нищо фантастично не се случва. Току-що добавихме TranslateModule и го конфигурирахме да използва HttpClient за зареждане на преводи. Експортирахме и TranslateModule, за да направим канала transform достъпен в AppModule и в HTML шаблони. В конструктора посочихме наличните езици и използвахме функция, предоставена от ngx-translate, за да получим и използваме езика по подразбиране на браузъра.

По подразбиране TranslateHttpLoader ще зареди преводи от папката /assets/i18n/, така че нека добавим няколко файла там.

/assets/i18n/en.json

{
  "compA": "Component A works",
  "compB": "Component B works"
}

/assets/i18n/ru.json

{
  "compA": "Компонент А работает",
  "compB": "Компонент Б работает"
}

Забележка: използваме един файл за всеки език. В по-сложни приложения нищо не ни ограничава да създаваме файлове въз основа на локал, напр. en-US.json, en-Gb.json. Те ще бъдат третирани по същество като отделни преводи.

С тази конфигурация трябва да можем да актуализираме нашите шаблони на компоненти, за да използваме низовете за превод вместо твърдо кодиран текст.

// comp-a.component.html
<p>{{'compA' | translate}}</p>
// comp-b.component.html
<p>{{'compB' | translate}}</p>

Стартирайте приложението и забележете, че използва преводите от файла en.json. Нека добавим компонент, който ще ни позволи да превключваме между двата езика.

ng g c select-language --inlineStyle --inlineTemplate

Актуализирайте съдържанието на файла select-language.component.ts.

Библиотеката ngx-translate ни позволява да превключваме езици чрез просто translate.use() API извикване. Също така ни позволява да определим текущо избрания език чрез запитване към свойството translate.currentLang.

Вмъкнете новия компонент във файла app.component.html след маркера h1.

Стартирайте приложението и вижте, че езикът вече може да се превключва в движение. Избирането на различен език ще поиска подходящия .json файл.

Сега, ако изберем езика ru и опресним браузъра, ще видим, че страницата все още се зарежда с избрания език en. Браузърът няма механизъм за запомняне на избрания език. Нека поправим това.

🙄 Запаметяване на избрания език

Общността на Angular е направила много плъгини, подобряващи функционалността на пакета ngx-translate. Едно от тях е точно това, от което се нуждаем —ngx-translate-cache. Следвайки инструкциите, ние (1) ще инсталираме пакета

npm install ngx-translate-cache --save

и (2) използвайте го вътре в I18nModule.

Сега, ако изберем езика ru и обновим браузъра, ще видим, че е запомнил нашия избор. Забележете, че избрахме 'Cookie' като място за съхраняване на избрания език. Изборът по подразбиране за тази опция е 'LocalStorage'. LocalStorage обаче не е достъпен на сървъра. Голяма част от тази статия е свързана с активирането на SSR, така че сме малко проактивни тук и съхраняваме избора на език на място, където сървърът може да го прочете.

Досега наистина нямаше нищо особено в тази статия. Просто следвахме инструкциите, публикувани в съответните пакети, и капсулирахме свързаната с i18n логика в отделен модул. Добавянето на SSR има някои трудни части, така че нека разгледаме по-отблизо.

*** Кодът до този момент е достъпен тук.

💪Част 2 от 6: Добавяне на SSR към приложението

„Angular CLI е невероятен“! По-специално, неговата функция за схеми ни позволява да добавяме нови възможности към приложението с помощта на проста команда. В този случай ще изпълним следната команда, за да добавим SSR възможности.

ng add @nguniversal/express-engine --clientProject ssr-with-i18n

Изпълнението на тази команда се актуализира и добави няколко файла.

Ако погледнем файла package.json, ще видим, че сега имаме няколко нови скрипта, които можем да изпълним. Двете най-важни са: (1) build:ssr и (2) serve:ssr . Нека изпълним тези команди и да видим какво ще се случи.

И двете команди се изпълняват успешно. Въпреки това, когато заредим уебсайта в браузъра, получаваме грешка.

TypeError: Cannot read property 'match' of undefined
    at new I18nModule (C:\Source\Random\ssr-with-i18n\dist\server\main.js:113153:35)

Малко разследване разкрива, че неуспешният код е:

browserLang.match(/en|ru/)

Променливата browserLang е undefined, което означава, че следният ред от код не работи:

const browserLang = translateCacheService.getCachedLanguage() || translate.getBrowserLang();

Това се случва, защото се опитваме да получим достъп до API, специфични за браузъра, по време на изобразяването от страна на сървъра. Дори името на функцията — getBrowserLang предполага, че тази функция няма да работи на сървъра. Ще се върнем към този проблем, но за момента нека го коригираме чрез твърдо кодиране на стойността на променливата browserLang:

const browserLang = 'en';

Изградете и сервирайте приложението отново. Този път няма грешка. Всъщност, ако погледнем мрежовия раздел на инструментите за разработчици, ще видим, че SSR работи! Преводите обаче не се случиха.

Нека видим защо това се случва. Обърнете внимание на фабричната функция, използвана в TranslateModule за зареждане на преводи: translateLoaderFactory . Тази функция използва HttpClient и знае как да зареди JSON файловете, съдържащи преводи от браузъра. Фабричната функция обаче не е достатъчно интелигентна, за да знае как да зареди тези файлове, докато сте в сървърната среда.

Това ни води до двата проблема, които трябва да разрешим:

ПРОБЛЕМ 1. Възможност за определяне на правилния език за зареждане както в клиентската, така и в сървърната среда (вместо твърдо кодиране на стойността на en).

ПРОБЛЕМ 2. Въз основа на средата използвайте подходящия механизъм за зареждане на JSON файла, съдържащ преводи.

Сега, когато проблемите са идентифицирани, нека разгледаме различни начини за разрешаване на тези проблеми.

🤔 Оценка на съществуващите опции

Има няколко начина, по които можем да накараме всичко да работи. Има приключен проблем в хранилището на ngx-translate, свързан с активирането на SSR — проблем #754. Там могат да бъдат намерени няколко решения на ПРОБЛЕМИ 1 и 2.

Съществуващо решение 1. Коригирайте чрез HttpInterceptor

Един от най-новите коментари по проблем #754 предлага да се използва решение, намерено в статията „Angular Universal: Как да добавите многоезична поддръжка?' за справяне с ПРОБЛЕМ 2. За съжаление, ПРОБЛЕМ 1 не е разгледан в статията. Авторът предлага корекция с помощта на HttpInterceptor, който коригира заявките за извличане на JSON файловете, докато са на сървъра.

Въпреки че решението работи, ми се струва малко неудобно да създам прихващач, който ще закърпи пътя на заявката. Освен това, защо трябва да правим допълнителна заявка (въпреки че е локална), когато сървърът има достъп до файловете чрез файловата система? Нека да видим какви други опции са налични.

Съществуващо решение 2. Коригирайте чрез директно импортиране на JSON файлове

Няколко скорошни коментара по същия проблем #754 предлагат импортиране на съдържанието на JSON файлове направо във файла, който дефинира нашия модул. След това можем да проверим в коя среда работим и да използваме TranslateHttpLoader по подразбиране или персонализирана, която използва импортирания JSON. Този подход предлага начин за справяне с ПРОБЛЕМ 2 чрез проверка на средата, в която се изпълнява кодът: if (isPlatformBrowser(platform)). Ще използваме подобна проверка на платформата по-късно в статията.

Моля, не правете това! Като импортирате JSON файлове, както е показано по-горе, те ще попаднат в пакета на браузъра. Цялата цел на използването на HttpLoader е, че той ще зареди необходимия езиков файл при поискване, което прави пакета на браузъра по-малък.

С този метод преводите за всички езици ще бъдат групирани заедно с изпълнението на JavaScript, което компрометира производителността.

Въпреки че и двете съществуващи решения предоставят решение за ПРОБЛЕМ 2, те имат своите недостатъци. Едно води до отправяне на ненужни заявки, а друго компрометира производителността. Нито един от тях не предлага решение за ПРОБЛЕМ 1.

🔋 По-добър начин — Предварителни условия

В следващите раздели ще предоставя две отделни решения на идентифицираните ПРОБЛЕМИ. И двете решения ще изискват следните предпоставки.

Предпоставка 1. Трябва да инсталираме и използваме зависимост, наречена cookie-parser.

Предпоставка 2. Разберете токена за инжектиране на Angular REQUEST

Предпоставка 1. Защо се нуждаем от анализатор на бисквитки?

Библиотеката ngx-translate-cache отговаря за създаването на бисквитка в клиента, когато потребителят избере езика. По подразбиране (въпреки че може да се конфигурира) бисквитката се нарича lang. В предстоящите решения ще ни трябва начин за достъп до тази бисквитка на сървъра. По подразбиране можем да получим достъп до необходимата ни информация от обекта req.headers.cookie във всеки от манипулаторите на експресни заявки. Стойността ще изглежда така:

lang=en; other-cookie=other-value

Това свойство съдържа цялата информация, от която се нуждаем, но трябва да анализираме lang out. Въпреки че е достатъчно просто, няма нужда да преоткриваме колелото, тъй като cookie-parser е Express междинен софтуер, който прави точно това, от което се нуждаем.

Инсталирайте необходимите зависимости.

npm install cookie-parser
npm install @types/cookie-parser -D

Актуализирайте файла server.ts, за да използвате инсталирания cookie-parser.

import * as cookieParser from 'cookie-parser';
app.use(cookieParser());

Под капака cookie-parser ще анализира бисквитките и ще ги съхранява като речников обект под req.cookies.

{
  "lang": "en",
  "other-cookie": "other-value"
}

Предпоставка 2. Токенът за инжектиране Angular REQUEST

Сега, когато имаме удобен начин за достъп до бисквитките от обекта на заявката, трябва да имаме достъп до обекта req в контекста на приложението Angular. Това може лесно да се направи с помощта на жетона за инжектиране REQUEST.

Ето очевидния факт: Токенът за инжектиране REQUEST е наличен под @nguniversal/express-engine/tokens. Ето един не толкова очевиден факт: типът за обекта req е Request, предоставен от дефинициите на типа на библиотеката express.

Това е важно и може да ни препъне. Ако това импортиране бъде забравено, машинописният текст ще приеме различен тип Request от Fetch API, който е наличен под lib.dom.d.ts. В резултат на това TypeScript няма да познава обекта req.cookies и ще го подчертае с червено.

Сега сме готови за решенията

Моля, направете мисловна снимка на контролната точка ЧАСТ 2 по-долу. Ще използваме този код като отправна точка за следващите две части от тази поредица, където ще проучим как да коригираме двата ПРОБЛЕМА, посочени по-рано.

ЧАСТ 2 Контролен пункт

*** Кодът до този момент е достъпен тук.

👌 Част 3 от 6: Решение 1 — Коригиране чрез предоставяне на отделен I18nModule за сървъра

В момента нашето приложение изглежда така:

Диаграмата по-горе показва пътя на изпълнение на кода, когато кодът се изпълнява в браузъра (зелен) и когато се изпълнява в сървъра (сив). Забележете, че в пътя от страната на клиента файлът, който зарежда цялото приложение (main.ts), импортира директно AppModule. На пътя от страната на сървъра основният файл импортира отделен модул, AppServerModule, който на свой ред импортира AppModule. Също така, забележете, че I18nModule е зависимост от AppModule, което означава, че кодът на I18nModule ще бъде изпълнен както в клиента, така и в сървъра.

В решението по-долу ще направим страната на браузъра да изглежда повече като страната на сървъра. Ще представим нов модул — AppBrowserModule . Това ще бъде модулът, който ще бъде стартиран. Също така ще преименуваме I18nModule на I18nBrowserModule и ще го преместим в импортирания на AppBrowserModule. И накрая, ще представим нов модул, I18nServerModule, който ще използва достъп до файловата система за зареждане на JSON файлове. Този модул ще бъде импортиран вътре в AppServerModule. Вижте получената структура по-долу:

По-долу е кодът на новия I18nServerModule.

Има две основни неща, които се случват в кода по-горе.

Първо, използваме токена за инжектиране REQUEST, предоставен от Angular, за да получим пълния обект на заявка. Използваме токена за достъп до обекта „бисквитки“, за да разберем какъв език е избрал потребителят в браузъра. Познавайки езика, ние извикваме метода use на класа TranslateService, така че нашият уебсайт да бъде изобразен на този език.

Второ, действието по-горе ще задейства нашия персонализиран механизъм за зареждане, дефиниран в класа TranslateFsLoader. В класа ние просто използваме стандартен API на възел, за да четем файлове от файловата система (fs).

Решение 1 Резюме

Това решение напълно разделя пътя на компилация за сървъра от пътя на компилация за браузъра. ПРОБЛЕМ 1 е разрешен поради translate.getBrowserLang(), съществуващ само в I18nBrowserModule, който никога няма да работи в сървърната среда.

ПРОБЛЕМ 2 се разрешава по подобен начин от всеки модул I18n — модулите сървър и клиент — използвайки техния собствен механизъм за зареждане на превод — съответно TranslateFsLoader и TranslateHttpLoader.

Харесвам тази опция, защото идва с ясно разделение между кода, който се изпълнява на сървъра, и кода, който се изпълнява в браузъра. Представянето на AppBrowserModule установява архитектурата за обработка на случаи, когато логиката на сървъра и клиента се различава значително.

Има обаче още един подход за справяне с тази задача. Продължавай да четеш!

*** Кодът до този момент е достъпен тук.

👌Част 4 от 6: Решение 2 — Осигурете всичко в един модул

Сега, след като разгледахме Решение 1, нека разгледаме друг начин. За разлика от Решение 1, това решение не изисква създаването на нови модули. Вместо това целият код ще бъде поставен вътре в I18nModule. Това може да се постигне с помощта на функцията isPlatformBrowser, предоставена от Angular framework.

Да се ​​върнем към „Част 2 Контролен пункт“.

git checkout step-2

Сега ще информираме I18nModule за платформата, в която работи, и ще използваме подходящия Loader в зависимост от средата — или TranslateFsLoader, създаден в предишната част, или TranslateHttpLoader, предоставен от библиотеката ngx-translate.

Добавете PLATFORM_ID към детайлите на translateLoaderFactory. Това ще ни позволи да изберем товарача във фабриката в зависимост от текущата платформа.

Сега фабричната функция ще използва подходящия Loader в зависимост от платформата. Подобни корекции трябва да се направят на constructor на I18nModule.

Ако се опитаме да създадем приложението сега, ще получим грешка.

Module not found: Error: Can't resolve 'fs' in 'C:\ssr-with-i18n\src\app\i18n'
Module not found: Error: Can't resolve 'path' in 'C:\ssr-with-i18n\src\app\i18n'

Това е така, защото зависимостите fs и path, които са стриктно зависимости на Node, сега се споменават във файла, който е компилиран за средата от страна на клиента.

Ние, като разработчици, знаем, че тези зависимости от страна на сървъра няма да бъдат използвани, защото са зад подходящи if изрази, но компилаторът не знае това.

Има лесно решение и за този проблем. Можем да уведомим нашия компилатор да не включва тези зависимости в пакета от страна на клиента, като използваме ново поле „браузър“ на файла package.json.

Добавете следното към файла package.json.

"browser": {
  "fs": false,
  "path": false
}

Сега всичко ще се компилира и работи точно както при предишното решение.

Решение 2 Резюме

Както ПРОБЛЕМ 1, така и ПРОБЛЕМ 2 се решават чрез отделяне на специфичния за браузъра код от специфичния за сървъра код чрез израз „if“, който оценява текущата платформа:

isPlatformBrowser(this.platform)

Сега, когато има само един път на компилация за двете платформи, fs и path зависимостите, които са стриктно зависимости на възли, причиняват грешка по време на компилация, когато процесът на компилация компилира пакет на браузъра. Това се решава чрез указване на тези зависимости в полето browser на файла package.json и задаване на техните стойности на false.

Харесвам тази опция, защото е по-проста от гледна точка на потребителското приложение. Няма нужда да създавате допълнителни модули.

Актуализация: Току-що научих, че проверките на платформата се считат за анти-модел, тъй като „платформа“ като цяло е ефимерно понятие. Въпреки че това решение може да изглежда по-просто и работи, решение 1 е за предпочитане.

*** Кодът до този момент е достъпен тук.

⚡ Част 5 от 6: Подобрете производителността с TransferState

Ако стартираме нашето приложение в текущото му състояние и погледнем мрежовия раздел на инструментите за разработчици на браузъра, ще видим, че след първоначалното зареждане приложението ще направи заявка за зареждане на JSON файла за текущо избрания език.

Това няма много смисъл, тъй като вече сме заредили подходящия език в сървъра.

Извършването на допълнителна заявка за зареждане на вече заредени езикови преводи може да изглежда като не голям проблем, който си струва да бъде разрешен. Вероятно има области на приложение, които ще доведат до по-голяма печалба от гледна точка на настройка на производителността. Вижте тази статия за повече информация по тази тема. За по-големи приложения обаче файловете за превод също могат да станат по-големи. Следователно времето за тяхното изтегляне и обработка също ще се увеличи, в който момент това би било проблем за решаване.

За щастие, Angular Universal предоставя инструмент за решаване на този проблем с относително малко усилия: TransferState. Когато това решение се използва, сървърът ще вгради данните с първоначалния HTML, изпратен на клиента. Тогава клиентът ще може да прочете тези данни, без да е необходимо да пита сървъра.

Преглед на работния процес

За да използваме функцията TransferState, трябва да:

1. Добавете модула, предоставен от Angular за сървъра и за клиента: ServerTransferStateModule и BrowserTransferStateModule

2. На сървъра: задайте данните, които искаме да прехвърлим под определен ключ, използвайки API: transferState.set(key, value)

3. На клиента: извлечете данните с помощта на API: transferState.get(key, defaultValue)

Нашата реализация

Първо, нека добавим модулите TransferState към импортирания:

Сега нека направим съответните промени в I18nModule. Фрагментът по-долу показва новия код.

Второ, translateLoaderFactory сега ще изглежда така:

TranslateFsLoader вече ще използва TransferState:

Как точно прехвърля държавата? По време на изобразяването от страна на сървъра рамката ще включва данните в HTML <script> тага. Вижте това на изображението по-долу.

След като пакетът от страна на клиента се стартира, той ще има достъп до тези данни.

Сега трябва да позволим на клиентския Loader да използва прехвърлените данни. В момента нашата фабрична функция за зареждане просто връща TranslateHttpLoader. Ще трябва да създадем персонализиран товарач, който също ще може да обработва състоянието на прехвърляне.

Нека създадем нов файл, който да съдържа потребителския клас за зареждане. Новият товарач ще изглежда като този по-долу.

Актуализирайте translateLoaderFactory, за да използвате новия Loader:

Резюме на TransferState

Използването на TransferState ни позволи да избегнем зареждането на данни от браузъра, който вече е зареден на сървъра.

Сега, ако стартираме приложението, ще видим, че няма ненужна заявка към JSON файла за текущо избрания език в раздела мрежа.

*** Кодът до този момент е достъпен тук.

🤷‍Част 6 от 6: Стигнахме ли вече?

Независимо дали сте избрали решение 1 или 2, изглежда, че вече всичко работи! Нека затворим инструментите за разработчици на браузъра и да се насладим на усещането за постижение след много работа.

Само за да сме сигурни, че всичко работи правилно, нека актуализираме нашите JSON файлове и добавим „!!!“ в края на всички низове за превод, за да отпразнуваме. Създайте и стартирайте приложението. Обновете страницата и след това... почешете се по главата. „!!!“ не са там. Какво стана?

Докато панелът с инструменти за разработчици на браузъра беше отворен, браузърът изчистваше кеша при всяко презареждане на страницата. Това означава, че браузърът ще изтегля нови JSON файлове всеки път. В момента, в който затворихме панела с инструменти за разработчици, браузърът започна да кешира нашите активи. Въпреки че сме променили съдържанието на JSON файловете, браузърът не знае за това.

Но как браузърът знае да зарежда нови JavaScript и CSS файлове всеки път? Това е така, защото скриптовете за изграждане на Angular добавят уникален набор от знаци (хеш) към името на файла.

Този хеш се променя всеки път, когато се променя съдържанието на файла. Трябва да внедрим подобна функция за нашите JSON файлове.

За щастие изпълнението е лесно. Нека създадем папка /scripts и нов файл там: hash-translations.js.

За да работи този скрипт, трябва да инсталираме нова зависимост.

npm install md5 -D

Скрипт файлът по-горе дефинира две важни променливи: изходната директория и целевата директория.

Първо, скриптът ще изчисти целевата директория, ако не е празна. След това скриптът ще прегледа посочения изходен път, ще прочете JSON файловете и ще генерира хешове, използвайки md5 въз основа на съдържанието на файловете.

След като хешовете бъдат генерирани, скриптът ще напише копие на всеки файл в целевата директория, но този път с хеша в името на файла.

Накрая скриптът ще генерира файла map.json и ще го постави в целевата директория. Този файл ще ни позволи да изберем правилния хеширан файл въз основа на локала. Съдържанието на този файл ще изглежда по следния начин:

{
  "en": "[hash-for-file-1]",
  "ru": "[hash-for-file-2]"
}

Добавете запис към файла package.json под полето за скриптове, който ще ни позволи да изпълним създадения от нас файл.

Освен това актуализирайте скриптовете start и build:ssr, за да стартирате този нов скрипт:

Продължете и стартирайте новия скрипт, за да видите резултатите. Имайте предвид, че няма нужда да чекирате автоматично генерирани файлове в хранилището, тъй като те ще се променят често. Добавете запис към файла .gitignore.

src/assets/i18n/autogen/*

И накрая, актуализирайте зареждащите устройства за превод, за да обслужват автоматично генерирани файлове.

Пътят до файла, който трябва да заредим, изглежда така: ./assets/i18n/autogen/${lang}.${hash}.json. Частта ./assets/i18n/autogen/ е prefix. .${hash}.json е suffix. И двете променливи трябва да бъдат персонализирани, за да се използват автоматично генерираните файлове.

Ето как можем да се справим с промяната на префикса и за двата зареждащи устройства.

suffix ще трябва да се обработва в метода getTranslation на всеки зареждащ механизъм, тъй като имаме нужда от достъп до променливата lang.

Първо, трябва да се сдобием с автоматично генерирания map.json файл.

const i18nMap = require('../../assets/i18n/autogen/map.json');

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

TranslateBrowserLoader ще има следните промени:

За TranslateFsLoader това е промяна на кода от един ред.

Компилирайте и стартирайте приложението. Всичко ще работи според очакванията и браузърът ще зареди актуализирани файлове за превод, когато е необходимо.

*** Крайният код е достъпен тук.

👏 Резюме на статията

В тази статия изградихме поддържаемо решение за управление на низове за превод на приложения чрез отделни JSON файлове. Използвахме популярна библиотека — ngx-translate. Разгледахме и текущите решения за интегриране на тази функционалност с приложения, изобразени от страна на сървъра, предоставени от общността. Говорихме за слабостите на тези решения и предоставихме по-добри опции. И накрая, внедрихме няколко от разширените функции, като например: (1) запаметяване на избрания език чрез бисквитки, (2) използване на State Transfer за избягване на ненужни HTTP заявки към сървъра и (3) нарушаване на кеша за файловете за превод .

Специални благодарности на Ana Boca и Alex Bashmakov за прегледа, тестването и предоставянето на част от кода за тази статия.

Призив за действие

Винаги се радвам на обратна връзка, така че моля 👏, 📝 и се абонирайте за публикацията на AngularInDepth.