Ранее мы разработали способ генерировать HTML-страницы заранее, и это будет очень полезно при облачном хостинге. В этой статье мы запустим наше многоязычное приложение Angular с одной сборкой на клиентский хостинг Netlify и посмотрим, как оно будет тонуть или плавать.
- Netlify
- Приложения, управляемые файлами cookie
- Приложения, управляемые URL
- Фильтрация по запросу
- Angular APP_BASE_HREF
- APP_BASE_HREF в SSR
- Свободный конец
- Хостинг на Firebase и Surge
- РЕСУРСЫ
- Использование токена Angular APP_BASE_HREF для обслуживания многоязычных приложений и хостинг на Netlify, Angular — Sekrab Garage
Netlify
Чтобы разместить на Netlify приложение только для браузера, все файлы должны находиться в одной папке, включая любые внешние конфигурации. Сначала мы сделаем наш angular.json
, скопировав все необходимые активы в папку хоста. На StackBlitz найдите пример сборки в папке /host-netlify
.
В нашем angular.json
адаптируем задачу writeindex
:
// in writeindex target, the destination should also be the same folder
// if you are using gulp tasks, you need to adjust options as well
"writeindex": {
"builder": "./builder:localizeIndex",
"options": {
// source from netlify and place in same folder
"source": "host/client/placeholder.html",
"destination": "host/client",
// ...
},
Наш package.json
будет включать только две задачи
"build": "ng build --configuration=production",
"postbuild": "ng run cr:writeindex"
Установив netlify
глобально, переместитесь в папку вывода host
и запустите netlify dev
для локального тестирования. Сначала решение на основе cookie:
Приложения, управляемые файлами cookie
Netlify использует специальный файл cookie для идентификации языка: nf_lang
, это очень удобно в данной установке. Тогда netlify.toml
выглядит следующим образом:
# the publish folder
[build]
publish = "client/"
# netlfy.toml
[[redirects]]
from = "/*"
to = "/index.ar.html"
status = 200
# condition, cookie nf_lang set to "ar"
conditions = {Language = ["ar"]}
# all other languages, default to "en"
[[redirects]]
from = "/*"
to = "/index.en.html"
status = 200
В нашем конфиге (или во внешней конфигурации) установите имя cookie в nf_lang
.
// src/app/config.ts
export const Config = {
Res: {
cookieName: 'nf_lang',
//...
},
};
Запускаем локально, переключаем язык, работает. Двигаемся дальше. Хотелось бы, чтобы жизнь всегда была такой простой.
Приложения, управляемые URL
После сборки с base href
, установленным на нужный язык (например, /en/
), нам нужно переписать все активы в корневую папку:
/en/main.x.js
→ /main.x.js
.
Это правило перезаписи, а не физическая подача. Но как отличить его от дружественных URL? /en/products
, например? Есть три способа:
- Вручную установить правила для определенных маршрутов, а затем общий рерайт для всего остального. Это наименее эффективный способ, поэтому я не буду его рассматривать.
- Использование правила запроса Netlify для отличия активов от дружественных URL-адресов
- Полагаясь на сам Angular для выполнения тяжелой работы, используя токен APP_BASE_HREF, это мой предпочтительный вариант
Фильтрация по запросу
Мы можем различать URL, отправляемые в каждом навигационном запросе, с помощью строки запроса, например: nf_route
.
В нашем netlify.toml
# redirect anything with query nf_route
# plain redirects need to be handled first
[[redirects]]
from = "/en"
to = "/index.en.url.html"
status = 200
[[redirects]]
from = "/ar"
to = "/index.ar.url.html"
status = 200
# for every url with nf_route in query, redirect back to root
[[redirects]]
from = "/en/*"
to = "/index.en.url.html"
status = 200
query = {nf_route = "1"}
[[redirects]]
from = "/ar/*"
to = "/index.ar.url.html"
status = 200
query = {nf_route = "1"}
# catch resources and rewrite, this will fail if url is /en/products without query
[[redirects]]
from = "/:lang/*"
to = "/:splat"
status = 200
# if nothing matches redirect root to language cookie
[[redirects]]
from = "/*"
to = "/ar/"
status = 301
conditions = {Language = ["ar"]}
# default redirect
[[redirects]]
from = "/*"
to = "/en/"
status = 301
При переходе по адресу /en
обслуживается index.en.url.html
, и все остальные статические ресурсы под правилом /:lang/*
. Дружественный URL, например /en/products/details?nf_route=1
, также будет обслуживаться index.en.url.html
. Чтобы это работало, нам нужно добавить nf_route=1
ко всем маршрутам Angular. Один из способов сделать это — добавить слушатель события NavigationEnd
и использовать Location.go
, который заменяет URL без обновления.
В корневом компоненте или модуле App:
export class AppModule {
// Location from @angular/common
constructor(router: Router, location: Location) {
router.events
.pipe(filter((event) => event instanceof NavigationEnd))
.subscribe({
next: (e: NavigationEnd) => {
// for netlify query trick, add nf_route for all navigation
if (router.url.indexOf('nf_route=') < 0 ) {
// append it if it's not present
location.go(router.url, 'nf_route=1');
}
},
});
}
}
Один вариант использования может проскользнуть на мгновение, и это когда мы переходим к дружественному URL, который не содержит запроса. В результате мы получаем страницу 404.html
. Хостинг Netlify ловит это с помощью пользовательской страницы 404.html
. Мы можем использовать ее для перенаправления через JavaScript, если она не является активом.
<!-- in host/client, add a 404.html page -->
<!DOCTYPE html>
<html>
<body>
<script>
if (window.location.href.indexOf('.') < 0) {
// do nothing if it's a resource file, else add nf_route and replace
window.location.replace(window.location.href + '?nf_route=1');
}
</script>
</body>
</html>
Это также означает, что нам нужно добавить 404.html
в массив assets
в angular.json
. Это решение кажется немного халтурным. Давайте перейдем к лучшему решению.
Angular APP_BASE_HREF
Другое решение — положиться на Angular, чтобы обеспечить префикс языка в URL, не изменяя base href
документа. Для этого мы добавляем токен APP_BASE_HREF
в наши корневые провайдеры, значение которого зависит от внешнего языкового скрипта.
// in root app.module
@NgModule({
// ...
providers: [
{ provide: LOCALE_ID, useClass: LocaleId },
// here, provide APP_BASE_HREF, this will make URL based solution
// use value saved in Res class
{ provide: APP_BASE_HREF, useValue: '/' + Res.language }
],
})
export class AppModule { }
Это заставляет приложение работать с base href
, установленным в /
, и все маршруты имеют префикс языка, предоставляемого скриптом, присутствующим в index.[lang].html
. (В разработке всегда используется язык по умолчанию /en
).
Для использования этого метода необходима следующая настройка хостинга:
# netlify.toml with URL driven solution using APP_BASE_HREF token
# redirect anything with en or ar in root
# use the index file that has no language in the base href
[[redirects]]
from = "/en/*"
to = "/index.en.html" # not index.en.url.html
status = 200
[[redirects]]
from = "/ar/*"
to = "/index.ar.html"
status = 200
# if nothing matches redirect root to language cookie
[[redirects]]
from = "/*"
to = "/ar/"
status = 301
conditions = {Language = ["ar"]}
# default redirect
[[redirects]]
from = "/*"
to = "/en/"
status = 301
Все активы находятся относительно root и обслуживаются статически. Маршруты Angular переписываются. Это отличное решение.
APP_BASE_HREF в SSR
В SSR он почти никогда не нужен, даже если мы используем бессерверные функции, но давайте посмотрим, будет ли вышеописанное решение применимо в SSR. Мы столкнулись с похожей проблемой с LOCALE_ID
, значение обращается к скрипту слишком рано, и его нужно обновлять при маршрутизации. Поэтому вместо useValue
мы используем useClass
и расширяем класс String
:
// in app.module
{ provide: APP_BASE_HREF, useClass: RootHref }
Где-то в нашем приложении (найдите его в StackBlitz src/app/core/res.ts
)
// The RootHref class extends String
export class RootHref extends String {
// for browser platform this needs to be in constructor
constructor() {
super('/' + (cr.resources.language || 'en'));
}
}
Примечание: когда я поместил его в метод toString
, он не работал в браузерной платформе. У меня закончились свечи, поэтому я не стал копать глубже!
Свободный конец
Поскольку у нас нет серверного промежуточного ПО для сохранения выбранного языка в cookie, выбор пользователя может быть потерян при повторном посещении. Чтобы исправить это, достаточно обработчика события click
:
// in html template:
`(click)="saveLanguage(language.name)"
[href]="getLanguageLink(language.name)"`
// in component code, save cookie for next time
saveLanguage(lang: string) {
this.setCookie(lang, Config.Res.cookieName, 365);
}
Хостинг на Netlify только для браузера стоил того. Давайте пригласим на вечеринку других.
Хостинг на Firebase и Surge
Я начал писать конфигурацию хоста для Firebase, думая, что он может предложить больше, и для Surge, думая, что он никогда не будет работать. Но я был удивлен на обоих счетах. Следующий эпизод этой длинной серии по извращению локализации Angular будет посвящен Firebase и Surge. 😴
Спасибо, что дочитали до конца, а вы попадали в ловушку пересмотра старого кода, как я?
РЕСУРСЫ
- Проект StackBlitz
- Перенаправления конфигураций Netlify на основе файлов «.toml»
- Перенаправления запросов Netlify
- Токен Angular APP_BASE_HREF

Использование токена Angular APP_BASE_HREF для обслуживания многоязычных приложений и хостинг на Netlify, Angular — Sekrab Garage
Твистинг локализации Angular
