Многоязычное приложение Angular, размещенное на Firebase и Surge с одной и той же сборкой

В прошлый раз мы выяснили, как сделать префикс для всех маршрутов без изменения base href, используя токен APP_BASE_HREF в Angular, что помогло нам разместить на Netlify многоязычное приложение одной сборки. Сегодня мы попробуем Firebase и Surge.sh.

Хостинг на Firebase

Я начал писать конфигурацию хостинга для Firebase, думая, что он может предложить больше, но был разочарован. Конфигурация хостинга не позволяет выполнять условия, которые позволяет Netlify. В какой-то момент мы обратимся к бессерверной функции, которая не предусмотрена на тарифном плане Free. Давайте покопаемся.

Настраиваем Firebase локально

Чтобы иметь возможность тестировать локально, нам нужно иметь как минимум два файла в папке host и глобальный cli.

npm install -g firebase-tools

В StackBlitz найдите папку выделенного хоста Firebase в /host-firebase.

Два файла: .firebaserc и firebase.json.

// .firebaserc in the root of the firebase host
{
  "projects": {
    // does not have to exist in the cloud, we are running locally
    "default": "cr"
  }
}

Войдите в полноэкранный режим Выход из полноэкранного режима

firebase.json мы создадим с помощью правил хостинга, первоначально он выглядит следующим образом:

// firebase.json in the root of the firebase host
{
  "hosting": [
    {
      "target": "web",
            // the public folder is where the build will go
      "public": "client",
      "ignore": [
        "firebase.json",
        "**/.*",
        "**/node_modules/**"
      ],
      "rewrites": [
                // TODO
      ]
    }
  ]
}

Войти в полноэкранный режим Выйти из полноэкранного режима

Чтобы разместить на Firebase приложение только для браузера, все файлы должны находиться в одной папке, как в хостинге Netlify, включая любую внешнюю конфигурацию. На StackBlitz найдите пример сборки в папке /host-firebase. Наша задача writeindex также копируется в ту же папку host/client, что и при установке Netlify (найдите пример в /src/firebase/angular.json).

Чтобы запустить локально, перейдите в папку host и запустите:

firebase emulators:start

Browse to http://localhost:5000 и, надеюсь, вы увидите то же, что и я. Давайте начнем с приложения на основе URL:

Приложения на основе URL

Мы сталкиваемся с той же проблемой различения статических ресурсов и дружественных URL, что и в хостинге Netlify, но решение в виде условных правил недоступно в Firebase, поэтому мы сразу используем APP_BASE_HREF.

Поскольку все активы обслуживаются локально, нам просто нужно переписать дружественные URL-адреса

// firebase.json for URL based app with APP_BASE_URL set in language script
"rewrites": [
  {
    "source": "/ar{,/**}",
    "destination": "/index.ar.html"
  },
    // default others
  {
    "source": "**",
    "destination": "/index.en.html"
  }
]

Вход в полноэкранный режим Выйти из полноэкранного режима

Упущенная возможность — это / корневой URL для второго пользователя. Есть несколько решений для этого, некоторые из них хакерские: использование index.html, а другие требуют немного больше работы: полагаясь на Firebase i18n rewrite.

Использование корня index.html

Создайте пустую HTML-страницу со следующим сценарием

<!DOCTYPE html>
<html lang="en">
<head>
 <script>
  const cookie = document.cookie;
  let root = 'en';
  // use any cookie name, redirect according to value
  if (cookie.indexOf('cr-lang') > -1) {
    // this line was produced by CoPilot! Not bad!
    root = cookie.split('cr-lang=')[1].split(';')[0];
  }
  // replace URL, a client 301 redirect
  window.location.replace(`/${root}`);
 </script>
</head>
<body>

</body>
</html>

Войдите в полноэкранный режим Выйти из полноэкранного режима

Не забудьте добавить этот индексный файл к активам angular.json для копирования в папку client.

Используйте Firebase i18n rewrites

Чтобы это сработало, файлы должны быть физически сохранены в папках en и ar. Поэтому нам нужно немного изменить наш Angular builder или задачу gulp, чтобы поддерживать эту структуру папок:

|-client/
|----en/
|------index.html
|----ar/
|------index.html
|--assets...

Вход в полноэкранный режим Выход из полноэкранного режима

Тогда наш firebase.json будет выглядеть следующим образом

// to handle already selected language in cookies
// cookie name must be firebase-language-override
"i18n": {
  "root": "/"
},
"rewrites": [
  {
    "source": "/ar{,/**}",
    "destination": "/ar/index.html"
  },
  {
    "source": "**",
    "destination": "/en/index.html"
  }
]

Войти в полноэкранный режим Выход из полноэкранного режима

Мы можем легко настроить билдер для создания этой новой структуры, найдите новый код в StackBlitz в его собственном билдере builder/locales/folders.ts:

// update builder to build folder structure
interface Options  {
    // ...
    // new property
  fileName?: string;
}

export default createBuilder(LocalizeIndex);

function LocalizeIndex(
    options: Options,
    context: BuilderContext,
): BuilderOutput {
    // ...
        // instead of writing file, first create folder
    if (!existsSync(options.destination + '/' + lang.name)){
          mkdirSync(options.destination + '/' + lang.name);
    }
    // save file with index.html, base href = /
     writeFileSync(`${options.destination}/${lang.name}/${ options.fileName || 'index.html'}`, contents);

        // ...
    return { success: true };
}

Вход в полноэкранный режим Выход из полноэкранного режима

В нашем конфиге (или во внешней конфигурации) установите имя cookie на firebase-language-override.

// src/app/config.ts
export const Config = {
  Res: {
    cookieName: 'firebase-language-override',
        //...
  },
};

Войти в полноэкранный режим Выйти из полноэкранного режима

Собрали, запустили локально, переключили, работает. Давайте перейдем к решению на основе куки.

Вернемся немного назад к обычному приложению, без префикса APP_BASE_HREF. В Firebase нет правил условной перезаписи, однако есть решение i18n и бессерверные функции. Давайте попробуем оба варианта:

Firebase i18n rewrites:

Ниже приведена последовательность событий в файле хостинга:

  • переход по адресу /, хост определяет язык из браузера и обслуживает /en/index.html, который является физическим файлом
  • при переходе к /products, однако, в приложении Angular, хост попытается обслужить /en/products/index.html, который не существует, поэтому он попытается обслужить /en/404.html
  • Этот /en/404.html может иметь то же содержимое, что и файл /en/index.html и загружать само приложение Angular, но это плохая практика, поскольку он регистрируется как 404.
  • Мы также можем создать JavaScript-перенаправление в 404.html на /, но это тоже не лучшее решение, поскольку мы потеряем дружественный URL.

Структура папок будет выглядеть следующим образом

|-client/
|----en/
|------index.html
|------404.html
|----ar/
|------index.html
|------404.html
|--assets...

Вход в полноэкранный режим Выход из полноэкранного режима

Это слишком много причин, чтобы утомлять меня, ко всему прочему, локализованный 404.html не запускается локально в эмуляторе, поэтому я не смог его протестировать. Двигаемся дальше.

Бессерверная функция Firebase

В «бесплатном» плане Spark не работают функции в Firebase, первом платном плане: Однако Blaze довольно щедр в своем бесплатном уровне. Вы можете попробовать это локально, но для развертывания вам нужна подписка на Blaze.

Чтобы запустить функции локально, не имея реального приложения, вам нужно сделать следующее:

  • Создайте папку functions внутри папки firebase host (найдите ее в StackBlitz в разделе host-firebase).

  • Создайте файл functions/package.json.

    // package.json inside functions need nothing else
    {
        // that's the only dependency you need to install
      "dependencies": {
        "firebase-functions": "^3.22.0"
      },
      // add this, or in firebase.json add runtime, see below
      "engines": {
        "node": "16"
      }
    }
    
    
  • Добавьте узел engines в package.json, как указано выше, следуя документации firebase:

Файл package.json, созданный во время инициализации, содержит важный ключ: "engines": {"node": "10"}. Он определяет вашу версию Node.js для написания и развертывания функций. Вы можете выбрать другие поддерживаемые версии.

Или после сообщения об ошибке в моей командной строке, вы можете создать его в firebase.json:

// firebase.json in root of host, undocumented
{
    "hosting": ...
    "functions": {
        "runtime": "nodejs16"
    }
}

Войти в полноэкранный режим Выйти из полноэкранного режима
  • Установите единственную необходимую вам зависимость в папку functions firebase-functions.
  • На хосте запустите firebase emulators:start.
  • Игнорируйте забавные предупреждающие сообщения и просматривайте папку нормально

Функция, которую мы хотим создать, является универсальной для отправки правильного index.[lang].html в соответствии со значением cookie:

// functions/index.js
const functions = require('firebase-functions');

exports.serveLocalized = functions.https.onRequest((req, res) => {
  // find cr-lang cookie
    // Pssst: you can require a config.json
  // if you use external configuration to read the cookie name
  const cookie = req.headers.cookie;
  let lang = 'en';
  if (cookie.indexOf('cr-lang') > -1) {
    // CoPilot!
    lang = cookie.split('cr-lang=')[1].split(';')[0];
  }
  // serve the right index file
  res.status(200).sendFile(`index.${lang}.html`, {root: '../client/'});
});

Войти в полноэкранный режим Выйти из полноэкранного режима

В разделе hosting в firebase.json

// ...
"rewrites": [
  {
    "source": "**",
    "function": "serveLocalized"
  }
]

Войти в полноэкранный режим Выйти из полноэкранного режима

Теперь запустите эмулятор и перейдите по адресу http://locahost:5000.

В хостинге Firebase действует правило: если файл физически существует, то он будет обслуживаться первым. Таким образом, активы обслуживаются статически.

Ранее мы создавали внешнюю конфигурацию в Angular, одним из преимуществ этого проекта является то, что теперь мы можем иметь одну и ту же сборку на разных хостингах, каждый из которых имеет свою собственную конфигурацию. Мы также можем получить доступ к этой конфигурации в функциях Firebase с помощью простого оператора require.

Тестирование, переключение языков в cookies, работает. Двигаемся дальше.

Хостинг на Surge

Когда я решил попробовать разместить его на бесплатной подписке Surge, у меня было мало надежд, поскольку файл ROUTE не поддерживается. Но я был приятно удивлен. В Surge.sh есть инструмент публикации. Для использования убедитесь, что вы установили surge глобально:

npm install surge -g

Кроме того, файл CNAME должен находиться в папке published, мы включим его в наш массив assets в angular.json.

Для публикации переходим в папку host и запускаем surge ./client (предполагается, что мы собираем в подпапку client).

Найдите пример хоста в папке StackBlitz /host-surge.

Единственный доступный вариант

У нас не так много вариантов:

  • Приложениям на основе Cookie требуется условная перезапись, которая не поддерживается
  • Приложения на основе URL с base href, установленным на определенный язык, требуют перезаписи активов, что также не является опцией.
  • С APP_BASE_URL, обслуживаемый файл будет index.html, а если он не найден, то 200.html в корне, но в любом случае мы не будем знать, какой index.[lang].html обслуживать.

Это оставляет нам один вариант:

  • Использовать APP_BASE_HREF.
  • Создайте физические языковые папки для каждого языка.
|-client/
|----en/
|------200.html
|----ar/
|------200.html
|--assets...

Вход в полноэкранный режим Выход из полноэкранного режима
  • С помощью нашего конструктора Angular (или задачи Gulp) writeindex создайте 200.html в каждой языковой папке, которая будет обрабатывать перезапись /en/products/... в /en/200.html.
  • Вместо того чтобы делать index.html приложением Angular по умолчанию (что прекрасно работает), мы можем заставить его перенаправлять через JavaScript в нужную папку в соответствии с cookie.
<!DOCTYPE html>
<html lang="en">
<head>
 <script>
  const cookie = document.cookie;
  let root = 'en';
  // use any cookie name, redirect according to value
  if (cookie.indexOf('cr-lang') > -1) {
    root = cookie.split('cr-lang=')[1].split(';')[0];
  }
  // replace URL, a client 301 redirect
  window.location.replace(`/${root}`);
 </script>
</head>
<body>
</body>
</html>

Вход в полноэкранный режим Выход из полноэкранного режима

Запуск, перенаправление, переключение языка, празднование. 💃🏼💃🏼

Я действительно впечатлен и доволен Surge.

Заключение

Последним кусочком головоломки, чтобы подкрутить локализацию Angular, является извлечение переводимых ключей в нужный JavaScript файл. Чтобы сделать это и завершить нашу серию статей, возвращайтесь на следующей неделе. 😴

РЕСУРСЫ

  • Проект StackBlitz
  • Конфигурация хостинга Firebase
  • Переписывание i18n в Firebase
  • Тестируем Firebase локально
  • Surge HostingБлагодарим вас за чтение еще одной длинной статьи, вы заметили загогулины под камнем?

СООТВЕТСТВУЮЩИЕ ПОСТЫ

  • Загрузка внешних конфигураций в Angular Universal

Многоязычное приложение Angular, размещенное на Firebase и Surge с одной сборкой, Angular — Sekrab Garage

Твистинг локализации Angular

garage.sekrab.com

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