2022-09-29 08:01:37 +00:00
|
|
|
import * as gu from 'test/nbrowser/gristUtils';
|
|
|
|
import {server, setupTestSuite} from 'test/nbrowser/testUtils';
|
|
|
|
import {assert, driver} from 'mocha-webdriver';
|
|
|
|
import * as testUtils from 'test/server/testUtils';
|
|
|
|
import {getAppRoot} from 'app/server/lib/places';
|
|
|
|
import fetch from "node-fetch";
|
|
|
|
import fs from "fs";
|
|
|
|
import os from "os";
|
|
|
|
import path from 'path';
|
|
|
|
|
2024-04-10 17:32:41 +00:00
|
|
|
|
|
|
|
// We only support those formats for now:
|
|
|
|
// en.client.json
|
|
|
|
// en_US.client.json
|
|
|
|
// en_US.server.json
|
|
|
|
// zh_Hant.client.json
|
|
|
|
// {lang code (+ maybe with underscore and country code}.{namespace}.json
|
|
|
|
//
|
|
|
|
// Only this format was tested and is known to work.
|
|
|
|
|
|
|
|
const VALID_LOCALE_FORMAT = /^[a-z]{2,}(_\w+)?\.(\w+)\.json$/;
|
|
|
|
|
2022-09-29 08:01:37 +00:00
|
|
|
describe("Localization", function() {
|
2023-06-27 06:11:08 +00:00
|
|
|
this.timeout(60000);
|
2022-09-29 08:01:37 +00:00
|
|
|
setupTestSuite();
|
|
|
|
|
|
|
|
before(async function() {
|
|
|
|
const session = await gu.session().personalSite.anon.login();
|
|
|
|
await session.loadRelPath("/");
|
|
|
|
});
|
|
|
|
|
|
|
|
it("uses default options for English language", async function() {
|
|
|
|
// Currently, there is not much translated, so test just what we have.
|
|
|
|
assert.equal(await driver.findWait('.test-welcome-title', 3000).getText(), 'Welcome to Grist!');
|
|
|
|
// Grist config should contain the list of supported languages;
|
|
|
|
const gristConfig: any = await driver.executeScript("return window.gristConfig");
|
|
|
|
|
2022-10-13 10:05:19 +00:00
|
|
|
// client and en is required.
|
|
|
|
assert.isTrue(gristConfig.namespaces.includes("client"));
|
2022-09-29 08:01:37 +00:00
|
|
|
assert.isTrue(gristConfig.supportedLngs.includes("en"));
|
|
|
|
});
|
|
|
|
|
|
|
|
it("loads all files from resource folder", async function() {
|
|
|
|
if (server.isExternalServer()) {
|
|
|
|
this.skip();
|
|
|
|
}
|
|
|
|
// Grist config should contain the list of supported languages;
|
|
|
|
const gristConfig: any = await driver.executeScript("return window.gristConfig");
|
|
|
|
// Should report all supported languages and namespaces.
|
|
|
|
const localeDirectory = path.join(getAppRoot(), 'static', 'locales');
|
|
|
|
// Read all file names from localeDirectory
|
|
|
|
const langs: Set<string> = new Set();
|
|
|
|
const namespaces: Set<string> = new Set();
|
|
|
|
for (const file of fs.readdirSync(localeDirectory)) {
|
2024-04-10 17:32:41 +00:00
|
|
|
// Make sure we see only valid files.
|
|
|
|
assert.match(file, VALID_LOCALE_FORMAT);
|
|
|
|
const langRaw = file.split('.')[0];
|
|
|
|
const lang = langRaw?.replace(/_/g, '-');
|
|
|
|
const ns = file.split('.')[1];
|
|
|
|
const clientFile = path.join(localeDirectory,
|
|
|
|
`${langRaw}.client.json`);
|
|
|
|
const clientText = fs.readFileSync(clientFile, { encoding: 'utf8' });
|
|
|
|
if (!clientText.includes('Translators: please translate this only when')) {
|
|
|
|
// Translation not ready if this key is not present.
|
|
|
|
continue;
|
2022-09-29 08:01:37 +00:00
|
|
|
}
|
2024-04-10 17:32:41 +00:00
|
|
|
langs.add(lang);
|
|
|
|
namespaces.add(ns);
|
2022-09-29 08:01:37 +00:00
|
|
|
}
|
|
|
|
assert.deepEqual(gristConfig.supportedLngs.sort(), [...langs].sort());
|
|
|
|
assert.deepEqual(gristConfig.namespaces.sort(), [...namespaces].sort());
|
2023-04-03 16:55:08 +00:00
|
|
|
assert.isAbove(gristConfig.supportedLngs.length, 9);
|
2022-09-29 08:01:37 +00:00
|
|
|
});
|
|
|
|
|
2023-02-03 23:56:24 +00:00
|
|
|
// Now make a uz-UZ language file, and test that it is used.
|
|
|
|
describe("with uz-UZ language file", function() {
|
2022-09-29 08:01:37 +00:00
|
|
|
let oldEnv: testUtils.EnvironmentSnapshot;
|
|
|
|
let tempLocale: string;
|
2022-10-28 16:11:08 +00:00
|
|
|
let existingLocales: string[];
|
2022-09-29 08:01:37 +00:00
|
|
|
before(async function() {
|
|
|
|
if (server.isExternalServer()) {
|
|
|
|
this.skip();
|
|
|
|
}
|
2022-10-28 16:11:08 +00:00
|
|
|
const gristConfig: any = await driver.executeScript("return window.gristConfig");
|
|
|
|
existingLocales = gristConfig.supportedLngs;
|
2022-09-29 08:01:37 +00:00
|
|
|
oldEnv = new testUtils.EnvironmentSnapshot();
|
|
|
|
// Add another language to the list of supported languages.
|
|
|
|
tempLocale = makeCopy();
|
2023-02-03 23:56:24 +00:00
|
|
|
createLanguage(tempLocale, "uz");
|
2022-09-29 08:01:37 +00:00
|
|
|
process.env.GRIST_LOCALES_DIR = tempLocale;
|
|
|
|
await server.restart();
|
|
|
|
});
|
|
|
|
|
|
|
|
after(async () => {
|
|
|
|
oldEnv.restore();
|
|
|
|
await server.restart();
|
|
|
|
});
|
|
|
|
|
|
|
|
it("detects correct language from client headers", async function() {
|
|
|
|
const homeUrl = `${server.getHost()}/o/docs`;
|
|
|
|
// Read response from server, and check that it contains the correct language.
|
|
|
|
const enResponse = await (await fetch(homeUrl)).text();
|
2023-02-03 23:56:24 +00:00
|
|
|
const uzResponse = await (await fetch(homeUrl, {headers: {"Accept-Language": "uz-UZ,uz;q=1"}})).text();
|
2022-09-29 08:01:37 +00:00
|
|
|
const ptResponse = await (await fetch(homeUrl, {headers: {"Accept-Language": "pt-PR,pt;q=1"}})).text();
|
2024-04-10 17:32:41 +00:00
|
|
|
// We have file with nb_NO code, but still this should be preloaded.
|
|
|
|
const noResponse = await (await fetch(homeUrl, {headers: {"Accept-Language": "nb-NO,nb;q=1"}})).text();
|
2022-09-29 08:01:37 +00:00
|
|
|
|
|
|
|
function present(response: string, ...langs: string[]) {
|
|
|
|
for (const lang of langs) {
|
2022-10-13 10:05:19 +00:00
|
|
|
assert.include(response, `href="locales/${lang}.client.json"`);
|
2022-09-29 08:01:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function notPresent(response: string, ...langs: string[]) {
|
|
|
|
for (const lang of langs) {
|
2022-10-13 10:05:19 +00:00
|
|
|
assert.notInclude(response, `href="locales/${lang}.client.json"`);
|
2022-09-29 08:01:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// English locale is preloaded always.
|
|
|
|
present(enResponse, "en");
|
2023-02-03 23:56:24 +00:00
|
|
|
present(uzResponse, "en");
|
2022-09-29 08:01:37 +00:00
|
|
|
present(ptResponse, "en");
|
2024-04-10 17:32:41 +00:00
|
|
|
present(noResponse, "en");
|
2022-09-29 08:01:37 +00:00
|
|
|
|
|
|
|
// Other locales are not preloaded for English.
|
2023-02-03 23:56:24 +00:00
|
|
|
notPresent(enResponse, "uz", "un-UZ", "en-US");
|
2022-09-29 08:01:37 +00:00
|
|
|
|
2023-02-03 23:56:24 +00:00
|
|
|
// For uz-UZ we have additional uz locale.
|
|
|
|
present(uzResponse, "uz");
|
|
|
|
// But only uz code is preloaded.
|
|
|
|
notPresent(uzResponse, "uz-UZ");
|
2022-09-29 08:01:37 +00:00
|
|
|
|
2023-06-30 09:50:40 +00:00
|
|
|
notPresent(ptResponse, "pt-PR", "uz", "en-US");
|
2024-04-10 17:32:41 +00:00
|
|
|
|
|
|
|
// For no-NO we have nb_NO file.
|
|
|
|
present(noResponse, "nb_NO");
|
2022-09-29 08:01:37 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
it("loads correct languages from file system", async function() {
|
2022-12-13 16:26:42 +00:00
|
|
|
modifyByCode(tempLocale, "en", {HomeIntro: {'Welcome to Grist!': 'TestMessage'}});
|
2022-09-29 08:01:37 +00:00
|
|
|
await driver.navigate().refresh();
|
|
|
|
assert.equal(await driver.findWait('.test-welcome-title', 3000).getText(), 'TestMessage');
|
|
|
|
const gristConfig: any = await driver.executeScript("return window.gristConfig");
|
2023-02-03 23:56:24 +00:00
|
|
|
assert.sameDeepMembers(gristConfig.supportedLngs, [...existingLocales, 'uz']);
|
2022-09-29 08:01:37 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2022-09-30 14:45:28 +00:00
|
|
|
it("breaks the server if something is wrong with resource files", async function() {
|
|
|
|
if (server.isExternalServer()) {
|
|
|
|
this.skip();
|
|
|
|
}
|
2022-09-29 08:01:37 +00:00
|
|
|
const oldEnv = new testUtils.EnvironmentSnapshot();
|
|
|
|
try {
|
|
|
|
// Wrong path to locales.
|
|
|
|
process.env.GRIST_LOCALES_DIR = __filename;
|
2022-10-31 14:46:02 +00:00
|
|
|
await assert.isRejected(server.restart(false, true));
|
2022-09-29 08:01:37 +00:00
|
|
|
// Empty folder.
|
|
|
|
const tempDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'grist_test_'));
|
|
|
|
process.env.GRIST_LOCALES_DIR = tempDirectory;
|
2022-10-31 14:46:02 +00:00
|
|
|
await assert.isRejected(server.restart(false, true));
|
2022-09-29 08:01:37 +00:00
|
|
|
// Wrong file format.
|
|
|
|
fs.writeFileSync(path.join(tempDirectory, 'dummy.json'), 'invalid json');
|
2022-10-31 14:46:02 +00:00
|
|
|
await assert.isRejected(server.restart(false, true));
|
2022-09-29 08:01:37 +00:00
|
|
|
} finally {
|
|
|
|
oldEnv.restore();
|
|
|
|
await server.restart();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a new language by coping existing "en" resources.
|
|
|
|
*/
|
|
|
|
function createLanguage(localesPath: string, code: string) {
|
|
|
|
for (const file of fs.readdirSync(localesPath)) {
|
2022-10-28 16:11:08 +00:00
|
|
|
if (file.startsWith('en.')) {
|
|
|
|
const newFile = file.replace('en', code);
|
|
|
|
fs.copyFileSync(path.join(localesPath, file), path.join(localesPath, newFile));
|
|
|
|
}
|
2022-09-29 08:01:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Makes a copy of all resource files and returns path to the temporary directory.
|
|
|
|
*/
|
|
|
|
function makeCopy() {
|
|
|
|
const tempDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'grist_test_'));
|
|
|
|
const localeDirectory = path.join(getAppRoot(), 'static', 'locales');
|
|
|
|
// Copy all files from localeDirectory to tempDirectory.
|
|
|
|
fs.readdirSync(localeDirectory).forEach(file => {
|
|
|
|
fs.copyFileSync(path.join(localeDirectory, file), path.join(tempDirectory, file));
|
|
|
|
});
|
|
|
|
return tempDirectory;
|
|
|
|
}
|
|
|
|
|
|
|
|
function modifyByCode(localeDir: string, code: string, obj: any) {
|
2022-10-13 10:05:19 +00:00
|
|
|
// Read current client localization file.
|
|
|
|
const filePath = path.join(localeDir, `${code}.client.json`);
|
2022-09-29 08:01:37 +00:00
|
|
|
const resources = JSON.parse(fs.readFileSync(filePath).toString());
|
|
|
|
const newResource = Object.assign(resources, obj);
|
|
|
|
fs.writeFileSync(filePath, JSON.stringify(newResource));
|
|
|
|
}
|
|
|
|
});
|