В прошлый раз мы выяснили, как сделать префикс для всех маршрутов без изменения base href
, используя токен APP_BASE_HREF
в Angular, что помогло нам разместить на Netlify многоязычное приложение одной сборки. Сегодня мы попробуем Firebase и Surge.sh.
- Хостинг на Firebase
- Настраиваем Firebase локально
- Приложения на основе URL
- Использование корня index.html
- Используйте Firebase i18n rewrites
- Приложения на основе cookie
- Firebase i18n rewrites:
- Бессерверная функция Firebase
- Хостинг на Surge
- Единственный доступный вариант
- Заключение
- РЕСУРСЫ
- СООТВЕТСТВУЮЩИЕ ПОСТЫ
- Многоязычное приложение Angular, размещенное на Firebase и Surge с одной сборкой, Angular — Sekrab Garage
Хостинг на 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',
//...
},
};
Собрали, запустили локально, переключили, работает. Давайте перейдем к решению на основе куки.
Приложения на основе cookie
Вернемся немного назад к обычному приложению, без префикса 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
внутри папки firebasehost
(найдите ее в 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
