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

Ранее мы разработали способ генерировать HTML-страницы заранее, и это будет очень полезно при облачном хостинге. В этой статье мы запустим наше многоязычное приложение Angular с одной сборкой на клиентский хостинг Netlify и посмотрим, как оно будет тонуть или плавать.

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:

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

garage.sekrab.com

Оцените статью
devanswers.ru
Добавить комментарий