Files
NodeBB/test/i18n.js

174 lines
5.6 KiB
JavaScript
Raw Normal View History

2021-10-14 09:38:50 -04:00
'use strict';
// For tests relating to the translator module, check translator.js
const assert = require('assert');
const path = require('path');
const fs = require('fs');
const file = require('../src/file');
const db = require('./mocks/databasemock');
describe('i18n', () => {
let folders;
before(async function () {
2023-09-07 11:49:35 -04:00
if ((process.env.GITHUB_REF && process.env.GITHUB_REF !== 'refs/heads/develop') || process.env.GITHUB_EVENT_NAME === 'pull_request') {
this.skip();
}
2021-10-14 09:38:50 -04:00
folders = await fs.promises.readdir(path.resolve(__dirname, '../public/language'));
2021-10-26 14:51:49 -04:00
folders = folders.filter(f => f !== 'README.md');
2021-10-14 09:38:50 -04:00
});
it('should contain folders named after the language code', async () => {
2021-11-18 16:42:18 -05:00
const valid = /(?:README.md|^[a-z]{2}(?:-[A-Z]{2})?$|^[a-z]{2}(?:-x-[a-z]+)?$)/; // good luck
2021-10-14 09:38:50 -04:00
folders.forEach((folder) => {
assert(valid.test(folder));
});
});
// There has to be a better way to generate tests asynchronously...
it('', async () => {
const sourcePath = path.resolve(__dirname, '../public/language/en-GB');
const fullPaths = await file.walk(sourcePath);
const sourceFiles = fullPaths.map(path => path.replace(sourcePath, ''));
const sourceStrings = new Map();
describe('source language file structure', () => {
const test = /^[a-zA-Z0-9-/]+(\.([0-9a-z]+([A-Z][0-9a-zA-Z]*)*-*\.?)+)*$/; // enhanced by chatgpt so only it knows what this does.
2021-10-14 09:38:50 -04:00
it('should only contain valid JSON files', async () => {
try {
fullPaths.forEach((fullPath) => {
2022-06-17 14:26:12 -04:00
if (fullPath.endsWith('_DO_NOT_EDIT_FILES_HERE.md')) {
return;
}
2021-10-14 09:38:50 -04:00
const hash = require(fullPath);
sourceStrings.set(fullPath.replace(sourcePath, ''), hash);
});
} catch (e) {
assert(!e, `Invalid JSON found: ${e.message}`);
}
});
describe('should only contain lowercase or numeric language keys separated by either dashes or periods', async () => {
describe('(regexp validation)', () => {
const valid = [
'foo.bar', 'foo.bar-baz', 'foo.bar.baz-quux-lorem-ipsum-dolor-sit-amet', 'foo.barBazQuux', // human generated
2023-10-13 17:19:22 -04:00
'example-name.isValid', 'kebab-case.isGood', 'camelcase.isFine', 'camelcase.with-dashes.isAlsoFine', 'single-character.is-ok', 'abc.def', // chatgpt generated
];
const invalid = [
// human generated
'foo.PascalCase', 'foo.snake_case',
'badger.badger_badger_badger',
'foo.BarBazQuux',
// chatgpt generated
2023-10-13 17:19:22 -04:00
'!notValid', // Starts with a special character
'with space.isInvalid', // Contains a space
'.startsWithPeriod.isInvalid', // Starts with a period
'invalid..case.isInvalid', // Consecutive periods
'camelCase.With-Dashes.isAlsoInvalid', // PascalCase "With" is not allowed
];
valid.forEach((key) => {
it(key, () => {
2023-10-13 17:19:22 -04:00
assert(test.test(key));
});
});
invalid.forEach((key) => {
it(key, () => {
2023-10-13 17:19:22 -04:00
assert(!test.test(key));
});
});
});
fullPaths.forEach((fullPath) => {
if (fullPath.endsWith('_DO_NOT_EDIT_FILES_HERE.md')) {
return;
}
const hash = require(fullPath);
const keys = Object.keys(hash);
keys.forEach((key) => {
it(key, () => {
assert(test.test(key), `${key} contains invalid characters`);
});
});
});
});
2021-10-14 09:38:50 -04:00
});
folders.forEach((language) => {
describe(`"${language}" file structure`, () => {
let files;
before(async () => {
const translationPath = path.resolve(__dirname, `../public/language/${language}`);
files = (await file.walk(translationPath)).map(path => path.replace(translationPath, ''));
});
it('translations should contain every language file contained in the source language directory', () => {
sourceFiles.forEach((relativePath) => {
assert(files.includes(relativePath), `${relativePath.slice(1)} was found in source files but was not found in language "${language}" (likely not internationalized)`);
});
});
it('should not contain any extraneous files not included in the source language directory', () => {
files.forEach((relativePath) => {
assert(sourceFiles.includes(relativePath), `${relativePath.slice(1)} was found in language "${language}" but there is no source file for it (likely removed from en-GB)`);
});
});
});
describe(`"${language}" file contents`, () => {
let fullPaths;
const translationPath = path.resolve(__dirname, `../public/language/${language}`);
const strings = new Map();
before(async () => {
fullPaths = await file.walk(translationPath);
});
it('should contain only valid JSON files', () => {
try {
fullPaths.forEach((fullPath) => {
2022-06-17 14:26:12 -04:00
if (fullPath.endsWith('_DO_NOT_EDIT_FILES_HERE.md')) {
return;
}
2021-10-14 09:38:50 -04:00
const hash = require(fullPath);
strings.set(fullPath.replace(translationPath, ''), hash);
});
} catch (e) {
assert(!e, `Invalid JSON found: ${e.message}`);
}
});
it('should contain every translation key contained in its source counterpart', () => {
2021-10-19 14:06:22 -04:00
const sourceArr = Array.from(sourceStrings.keys());
sourceArr.forEach((namespace) => {
const sourceKeys = Object.keys(sourceStrings.get(namespace));
const translationKeys = Object.keys(strings.get(namespace));
assert(sourceKeys && translationKeys);
sourceKeys.forEach((key) => {
assert(translationKeys.includes(key), `${namespace.slice(1, -5)}:${key} missing in ${language}`);
});
assert.strictEqual(
sourceKeys.length,
translationKeys.length,
`Extra keys found in namespace ${namespace.slice(1, -5)} for language "${language}"`
);
});
2021-10-14 09:38:50 -04:00
});
});
});
});
});