Обслуживание одной и той же сборки Angular с разными URL-адресами

Ранее мы создали сервер в Express для обслуживания одной и той же сборки Angular с другим языковым файлом, используя правила перезаписи для этого файла, обнаруживая cookie. Это хорошо работает для интрасетей и авторизованных приложений. Сегодня мы создадим наше приложение, чтобы вместо этого определять язык в URL. Например, так:

www.site.com/**en**/content/details

Преимущества 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

garage.sekrab.com

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