Ранее мы создали сервер в Express для обслуживания одной и той же сборки Angular с другим языковым файлом, используя правила перезаписи для этого файла, обнаруживая cookie. Это хорошо работает для интрасетей и авторизованных приложений. Сегодня мы создадим наше приложение, чтобы вместо этого определять язык в URL. Например, так:
www.site.com/**en**/content/details
- Преимущества URL-адреса с определением языка
- Приложение только для браузера
- Определение языка по URL
- Зачем нужна опция index: false.
- Замена индексной базы href
- Неподдерживаемые языки
- Корневой URL
- Серверная платформа
- Обслуживание немного больше, чем язык
- РЕСУРСЫ
- Обслуживание одной и той же сборки Angular с разными URL, Angular — Sekrab Garage
Преимущества URL-адреса с определением языка
Я могу назвать два преимущества наличия языка в URL:
- Путаница поисковых ботов: для публичного контента, который может быть просмотрен поисковыми ботами, если боты могут просматривать несколько языков, предоставление разного контента для одного и того же URL сбивает ботов с толку и влияет на рейтинг сайта.
- Локализация результатов: говоря о ботах, язык в URL позволяет нам иметь альтернативные ссылки в заголовке для разных языков, поисковые системы возвращают пользователям релевантное соответствие. Google утверждает, что
Обратите внимание, что языковые поддомены в этих URL (
en
,en-gb
,en-us
,de
) не используются Google для определения целевой аудитории страницы; вы должны явно указать целевую аудиторию.
Но опять же, Google не все документирует, у меня есть догадка, что это имеет значение.
- Второе преимущество — это удобство пользователей, если они случайно выберут язык, отличный от их локали. Сохраняя URL в избранном, открывая на разных устройствах или делясь им со своими друзьями, желательно, чтобы сам URL содержал информацию о языке, чтобы знать намерения пользователя.
Отлично, два преимущества в трех пунктах. Надеюсь, вы убедились. Давайте приступим.
Найдите файлы в StackBlitz, хотя многого не ждите, среда слишком строгая, чтобы позволить им запуститься.
Приложение только для браузера
Нам нужно выполнить следующее:
- Определить язык по URL в языковом промежуточном ПО
- Передавать правильное значение
base href
вindex.html
. - Перенаправить неподдерживаемые языки на язык по умолчанию
- Обработка корневого URL
Определение языка по URL
Начиная с языкового промежуточного ПО:
module.exports = function (config) {
return function (req, res, next) {
// exclude non html sources, for now exclude all resources with extension
if (req.path.indexOf('.') > 1) {
next();
return;
}
// derive language from url, the first segment of the URL, no checks yet
res.locals.lang = req.path.split('/')[1];
next();
};
}
Мы извлекаем первый сегмент URL независимо от того, что происходит. Ниже приведены маршруты: (найдите их в StackBlitz в каталоге /host/server/routes-url.js)
// express routes
module.exports = function (app, config) {
// reroute according to lang, does not matter what param is passed because it's already set
app.get('/:lang/locale/language.js', function (req, res) {
res.sendFile(config.getLangPath(res.locals.lang));
});
// use static files in client, but skip index
app.use('/:lang', express.static(config.rootPath + '/client', {index: false}));
// TODO: exclude unsupported languages
app.get('/:lang/*', function(req, res){
// TODO: here, develop an HTML template engine to replace the base href value
res.render(config.rootPath + `client/index.html`, {lang: res.locals.lang});
});
// nothing matches? redirect to /root
app.get('/*', function (req, res) {
// if none, redirect to default language (TODO: default language)
res.redirect(301, '/' + res.locals.lang + req.path);
});
};
Зачем нужна опция index: false
.
У нас не было проблем в приложении только для браузера в предыдущей статье; позволяя index.html
обслуживаться модулем express static, поскольку мы обслуживали статический файл. Теперь, когда мы собираемся разработать шаблонизатор для изменения index.html
, нам нужно отключить индекс по умолчанию для корневых URL в статическом промежуточном ПО. Таким образом, site.com/en/
не должен обслуживаться статическим промежуточным ПО, поэтому мы передаем опцию index: false
:
app.use('/:lang', express.static(config.rootPath + '/client', {index: false}));
Существуют и менее прямые методы: переименование index.html и изменение файла по умолчанию; вот лишь некоторые из них.
Замена индексной базы href
Первая задача в нашем списке задач — сгенерировать правильный базовый href для каждого обслуживаемого языка. Мы создадим простой HTML-шаблон, который заменит строку на выбранный язык. Мы можем разместить следующий код в любом месте на нашем сервере:
// in epxress routes
// ...
const fs = require('fs') // this engine requires the fs module
module.exports = function (app, config) {
// ...
app.engine('html', (filePath, options, callback) => {
// define the template engine
fs.readFile(filePath, (err, content) => {
if (err) return callback(err);
// replace base href tag, with the proper language
const rendered = content.toString()
.replace('<base href="/">', `<base href="/${options.lang}/">`);
return callback(null, rendered)
});
});
// setting the engine and views folder are not needed
// ...
app.get('/:lang/*', function(req, res){
// use the HTML engine to render
res.render(config.rootPath + `client/index.html`, {lang: res.locals.lang});
});
// ...
}
Неподдерживаемые языки
Другой проблемой является обнаружение неподдерживаемого языка и откат назад. В языковом промежуточном ПО нам нужно сначала найти язык, сравнить его со списком поддерживаемых языков, и если он не найден, вернуть язык по умолчанию. Давайте сначала добавим список поддерживаемых языков в наш config
(опять же, это личный выбор, это выглядит немного повсеместно, но для масштаба, это должно подойти).
// config.js
module.exports = {
// ...
// supported languages
languages: ['en', 'ar']
};
В нашем языковом промежуточном ПО:
// language middleware:
// derive language from url, the first segment of the URL,
// check if found in supported languages
res.locals.lang = config.languages.find(n => n === req.path.split('/')[1]) || 'en';
В наших маршрутах нам нужно позаботиться только об одном маршруте — том, который определяет язык. Поэтому для маршрута index.html
мы передадим массив всех поддерживаемых языков в качестве пути:
// routes, use only supported lanugages URLs
app.get(config.languages.map(n => `/${n}/*`), function(req, res){
// pass language found in language middleware
res.render(config.rootPath + `client/index.html`, {lang: res.locals.lang});
});
Корневой URL
Последний шаг — перенаправить корневой URL на существующий язык. Лучшим вариантом будет сначала попытаться получить cookie, прежде чем установить язык по умолчанию. Таким образом, бит cookie все еще полезен в нашем языковом промежуточном ПО.
// language middleware
module.exports = function (config) {
return function (req, res, next) {
// check cookies for language
res.locals.lang = req.cookies[config.langCookieName] || 'en';
// exclude non html sources, exclude all resources with extension
if (req.path.indexOf('.') > 1) {
next();
return;
}
// derive language from url, the first segment of the URL,
// then fall back to cookie
res.locals.lang = config.languages.find((n) => n === req.path.split('/')[1]) ||
res.locals.lang;
// set cookie for a year
res.cookie(config.langCookieName, res.locals.lang, {
expires: new Date(Date.now() + 31622444360),
});
next();
};
}
Затем в маршрутах добавляем последний маршрут:
(Это также позаботится о любых URL, которые ранее не имели префикса языка или имели префикс не поддерживаемого языка, что является сценарием, в который мы не хотим погружаться).
// nothing matches? redirect to /en/path
app.get('/*', function (req, res) {
res.redirect(301, '/' + res.locals.lang + req.path);
});
Серверная платформа
Практически то же самое, что и маршруты только для браузера. Нам не нужно создавать новый движок, движок шаблонов уже предоставлен Angular. Читая документацию ngExpressEngine
, свойство, которое рендерит HTML-файл, — document
.
// build routes in SSR and change language via url
// find it in stackblitz host/server/routes-ssr-url.js
const ssr = require('./main');
const fs = require('fs');
module.exports = function (app, config) {
// ngExpressEngine
app.engine('html', ssr.AppEngine);
app.set('view engine', 'html');
app.set('views', config.rootPath + '/client');
// reroute according to lang, does not matter what param is passed because its already set
app.get('/:lang/locale/language.js', function (req, res) {
res.sendFile(config.getLangPath(res.locals.lang));
});
// use static files in client, skip index.html
app.use(
'/:lang',
express.static(config.rootPath + '/client', { index: false })
);
// exclude unsupported languages
app.get(config.languages.map((n) => `/${n}/*`), function (req, res) {
// use Angular engine, pass a new string of HTML in document property
const content = fs.readFileSync(config.rootPath + `client/index.html`);
const rendered = content.replace('<base href="/">', `<base href="/${res.locals.lang}/">`);
// first attribute does not matter, it's the default in views folder
res.render('', {
req,
res,
// overwrite here
document: rendered
});
}
);
// nothing matches? redirect to /en/path
app.get('/*', function (req, res) {
res.redirect(301, '/' + res.locals.lang + req.path);
});
};
Обслуживание немного больше, чем язык
Существуют решения для перевода, которые переключают язык сайта без обновления (ngx-Translate — одно из них), но с этим есть несколько проблем. Одна из них — необходимость изменения не только языкового файла в index.html
. Мы уже адаптировали значение HTML base href
, что еще мы можем адаптировать? Давайте узнаем в следующем эпизоде. 😴
Спасибо, что дочитали до конца, я печатал с импровизированной повязкой на указательном пальце. Простите за мои мизбеллы.
Альтернативный способ локализации в Angular
Обслуживание многоязычного приложения Angular с помощью ExpressJS
Обслуживание одной и той же сборки Angular с разными URL-адресами
РЕСУРСЫ
- Разработка шаблонизаторов для Express
- Локализованные версии ваших страниц в Google
- Проект Stackblitz

Обслуживание одной и той же сборки Angular с разными URL, Angular — Sekrab Garage
Локализация с помощью Angular
