Многие уже наверняка знают, что в Mendix встроены средства локализации приложений, причем многоязычность реализована во множестве проектов. Локализация — частое требование у клиентов. И вот однажды, получив такое требование, я задумался: я знаю, как реализовать поддержку нескольких языков в приложении, но пока что еще не сделал ни одного с многоязычной страницей входа. Так что сегодня именно этим мы и займемся.

Безусловно, почти любое решение можно реализовать несколькими способами, но мы выберем и рассмотрим только один.

Что такое интернационализация?

Интернационализация (i18n) — это процесс разработки и подготовки проекта к использованию в различных регионах по всему миру. Локализация — это процесс создания версий проекта для разных языков.

Локализация включает в себя:

  • Извлечение текста для перевода.

  • Применение определенного формата данных для каждого языкового стандарта (локали).

Локаль указывает на регион, в котором говорят на конкретном языке или варианте языка, и может соответствовать, например, стране или географическому региону. Локаль определяет то, как будут обрабатываться и отображаться:

  • единицы измерения, включая дату и время, числа и валюты,

  • переведенные названия, включая часовые пояса, языки и страны.

https://www.mendix.com

Что нам понадобится

  • Mendix Studio Pro 8.12.5 — подойдет и другая версия, но в этом случает может потребоваться адаптировать руководство в зависимости от структуры папок приложения.

  • Visual Studio Code, но можно использовать и любой другой удобный редактор.

  • Система безопасности для приложения (App Security) должна быть включена.

Приступим

Решать эту задачу будем поэтапно.

  1. Поиск файлов login.js и login_i18n.js

Если приложение запускалось хотя бы раз, они будут в каталоге «deployment» проекта, который создается после первого локального запуска приложения.

Запустив приложение, нажмите «Project → Show Project Directory in Explorer» (Проект → Показать каталог проекта в Проводнике).

Щелкните правой кнопкой мыши папку проекта и откройте его в редакторе кода.

Перейдите в каталог «deployment -> web -> js». Нужные файлы на скриншоте выделены:

Расположение файлов «login.js» и «login_i18n.js»

2. Копирование файлов

Файлы мы нашли — теперь будем добавлять переводы.

Создайте подпапку «js» в папке «theme» и скопируйте туда из каталога «deployment» файлы «login_i18n.js» и «login.js». Результат должен выглядеть примерно так:

Файлы скопированы в подпапку «js» папки «theme»

3. Настройка файла login.html

Этот этап — необязателен, однако он поможет разобраться, что происходит, когда вы добавляете новый элемент.

Файл «login.html» находится в папке «theme» приложения. Добавьте новую метку с идентификатором «login-local-label» следующим образом:

<div>
<!-- New field introduced -->
<label id="login-local-label">Login in with your account</label>
<div id="loginMessage" class="alert alert-danger"></div>
<div class="form-group">

4. Добавление переводов и их отображение согласно языку браузера

Предположим, у нас будет три языка: голландский, немецкий и английский.

Замените содержимое файла «login_i18n.js» на приведенное ниже. Сам код пояснений не требует: мы просто получаем из браузера предпочитаемый язык пользователя и в соответствии с ним устанавливаем значение различных полей.

СОВЕТ. Если вы добавляете на страницу «login.html» новые элементы, у них должен быть перевод. Поэтому для каждого нового переводимого элемента добавляйте пару «ключ — значение». Файл «login.js» также нужно будет изменить — но это уже на следующем этапе.

В этом примере я уже выбрал значение «login-local-label».

//Customized login_i18n.js file to support multiple language logins
// Identify the language of the users browser
var language = navigator.language.toLowerCase();
// Dutch language
if (language == "nl"){
window.i18nMap = {
"username": "Gebruikersnaam",
"password": "Wachtwoord",
"loginButton": "Aanmelden",
"http401": "De opgegeven gebruikersnaam of het opgegeven wachtwoord is onjuist.",
"http404": "Server niet gevonden.",
"http500": "Er is een interne serverfout opgetreden.",
"http503": "Service niet beschikbaar.",
"httpdefault": "Aanmelden mislukt.",
"http402": "De huidige licentie staat niet toe dat er meer gebruikers inloggen.",
"http460": "U bent uitgelogd, omdat u ergens anders hebt ingelogd.",
"http419": "Uw sessie is verlopen. Vul uw gebruikersnaam en wachtwoord in om verder te gaan.",
"goHomeButton": "Terug naar de startpagina",
"http403": "U hebt niet genoeg rechten om deze pagina te openen. U kunt proberen aan te melden als een andere gebruiker.",
"cancel": "Annuleren",
"loginlocallabel": "Login in met uw account"
}
}
// German language
else if (language == "de"){
window.i18nMap = {
"username": "Nutzername",
"password": "Passwort",
"loginButton": "Anmeldung",
"http401": "Der von Ihnen eingegebene Benutzername oder Passwort ist falsch.",
"http404": "Server nicht gefunden.",
"http500": "Ein interner Serverfehler ist aufgetreten.",
"http503": "Dienst nicht verfügbar.",
"httpdefault": "Anmeldung fehlgeschlagen.",
"http402": "Die aktuelle Lizenz erlaubt nicht mehr Benutzern, sich anzumelden.",
"http460": "Sie wurden abgemeldet, weil Sie sich woanders angemeldet haben.",
"http419": "Deine Sitzung ist abgelaufen. Bitte geben Sie Ihren Benutzernamen und Ihr Passwort ein, um mit der Arbeit fortzufahren.",
"goHomeButton": "Zurück zur Startseite",
"http403": "Sie haben nicht genügend Berechtigungen, um auf diese Seite zuzugreifen. Sie können versuchen, als anderer Benutzer zu loggen.",
"cancel": "Stornieren",
"loginlocallabel": "Melden Sie sich mit Ihrem Konto an"
}
}
//Default is English language
else {
window.i18nMap = {
"username": "Username",
"password": "Password",
"loginButton": "Login",
"http401": "The username or password you entered is incorrect.",
"http404": "Server not found.",
"http500": "An internal server error occurred.",
"http503": "Service not available.",
"httpdefault": "Sign in failed.",
"http402": "The current license does not allow more users to sign in.",
"http460": "You were signed out, because you signed in somewhere else.",
"http419": "Your session has expired. Please enter your user name and password to continue working.",
"goHomeButton": "Back to homepage",
"http403": "You don't have enough permissions to access this page. You may try to logic as a different user.",
"cancel": "Cancel",
"loginlocallabel": "Login with your account"
}
}

5. Получение переводов в файле «login.js»

Если вы не изменяете файл «login.html», это можно пропустить — в противном случае следуйте указаниям.

Соответствующие изменения разделены на этапы (STEP) и снабжены пояснениями.

(function() {
var byId = function(id) {
return document.getElementById(id);
};
var loginForm = byId("loginForm"),
loginMessage = byId("loginMessage"),
usernameLabel = byId("usernameLabel"),
usernameInput = byId("usernameInput"),
passwordLabel = byId("passwordLabel"),
passwordInput = byId("passwordInput"),
loginButton = byId("loginButton"),
goHomeButton = byId("goHomeButton"),
// STEP 1: Get the control
loginlocallabel = byId("login-local-label");
var showMessage = function(str) {
loginMessage.textContent = str || "";
loginMessage.style.display = str ? "block" : "none";
};
var hideMessage = function() {
showMessage("");
};
var removeMessageCode = function(search) {
var searchParams = search.substring(1).split("&").filter(function(param) {
return param.split("=")[0] !== "messageCode";
});
return searchParams.length > 0 ? "?" + searchParams.join("&") : "";
}
var submit = function() {
loginButton.setAttribute("disabled", "disabled");
var xhr = new XMLHttpRequest(),
json = JSON.stringify({
action: "login",
params: {
username: usernameInput.value,
password: passwordInput.value
}
});
xhr.open("POST", "xas/" , true);
xhr.setRequestHeader("Content-type", "application/json");
xhr.onreadystatechange = function() {
if (xhr.readyState != 4) return;
var msg;
switch (xhr.status) {
case 200:
var url = /login\.html/.test(window.location.pathname) ? "index.html" : "index3.html";
window.location = url + removeMessageCode(window.location.search) + window.location.hash;
return;
case 400:
case 401:
case 403:
msg = i18nMap.http401;
break;
case 402:
msg = i18nMap.http402;
break;
case 404:
msg = i18nMap.http404;
break;
case 500:
msg = i18nMap.http500;
break;
case 503:
msg = i18nMap.http503;
break;
default:
msg = i18nMap.httpdefault;
}
showMessage(msg);
loginButton.removeAttribute("disabled");
}
xhr.send(json);
return false;
};
var goHome = function() {
var url = /login\.html/.test(window.location.pathname) ? "index.html" : "index3.html";
window.location = url;
};
if (i18nMap) {
var usernameText = i18nMap.username,
passwordText = i18nMap.password,
buttonText = i18nMap.loginButton,
goHomeButtonText = i18nMap.goHomeButton;
// Step 2 - Get the value for the control
loginlocallabelText = i18nMap.loginlocallabel;
if (usernameText) {
usernameLabel.textContent = usernameText;
usernameInput.setAttribute("placeholder", usernameText);
}
if (passwordText) {
passwordLabel.textContent = passwordText;
passwordInput.setAttribute("placeholder", passwordText);
}
if (buttonText) {
loginButton.value = buttonText;
}
if (goHomeButton && goHomeButtonText) {
goHomeButton.value = goHomeButtonText
}
// STEP 3a - Set the translated value for the element
if (loginlocallabel) {
loginlocallabel.innerHTML =  loginlocallabelText;
}
}
loginForm.onsubmit = submit;
usernameInput.onkeydown = hideMessage;
passwordInput.onkeydown = hideMessage;
if (goHomeButton) {
goHomeButton.onclick = goHome;
goHomeButton.style.display = "none";
}
usernameInput.focus();
if (window.location.search) {
var messageCodeParameter = window.location.search.substring(1).split("&").filter(function(item) {
return item.split("=")[0] === "messageCode";
})[0];
if (messageCodeParameter) {
var messageCode = messageCodeParameter.split("=")[1];
showMessage(window.i18nMap["http" + messageCode]);
if (messageCode === "403") goHomeButton.style.display = "";
}
}
var cookieParts = [
"originURI=" + location.pathname,
"max-age=" + (60 * 60 * 24 * 365),
];
if (window.location.protocol === "https:") {
cookieParts.push("SameSite=None", "Secure");
}
document.cookie = cookieParts.join(";");
})();

6. Открытие приложения в браузере и смена языка

А теперь проверим, что у нас получилось.

Для этого нужно изменить язык браузера и открыть страницу входа в приложение. Следуйте указаниям ниже и посмотрите, как меняется страница входа. Мы будем использовать Chrome.

  1. Добавьте в браузер соответствующие языки.

Откройте страницу chrome://settings и перейдите в раздел «Дополнительные → Языки».

Добавление языков в браузер Chrome

2. Переключите язык и откройте страницу входа в приложение.

Это можно сделать, нажав три точки напротив языка и выбрав Отображать Google Chrome на этом языке.

3. Обновите страницу.

Страница входа на голландском языке

Страница входа на голландском

Страница входа на немецком языке

Страница входа на немецком

Страница входа на английском языке (по умолчанию)

Страница входа по умолчанию

О переводчике

Перевод статьи выполнен в Alconost.

Alconost занимается локализацией игрприложений и сайтов на 70 языков. Переводчики-носители языка, лингвистическое тестирование, облачная платформа с API, непрерывная локализация, менеджеры проектов 24/7, любые форматы строковых ресурсов.

Мы также делаем рекламные и обучающие видеоролики — для сайтов, продающие, имиджевые, рекламные, обучающие, тизеры, эксплейнеры, трейлеры для Google Play и App Store.