mirror of
https://github.com/Hessenuk/DiscordTickets.git
synced 2024-12-23 00:03:09 +02:00
ci: test i18n
This commit is contained in:
parent
ea16eb704b
commit
80c23444c6
35
.github/workflows/i18n.yml
vendored
Normal file
35
.github/workflows/i18n.yml
vendored
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
name: 'Check localisations'
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
# branches:
|
||||||
|
# - main
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}'
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Setup node
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 18
|
||||||
|
cache: 'pnpm'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install
|
||||||
|
|
||||||
|
- name: Validate localisations
|
||||||
|
run: ./scripts/check-i18n.js
|
@ -14,7 +14,7 @@
|
|||||||
"postinstall": "node scripts/postinstall",
|
"postinstall": "node scripts/postinstall",
|
||||||
"start": "node .",
|
"start": "node .",
|
||||||
"studio": "npx prisma studio",
|
"studio": "npx prisma studio",
|
||||||
"test": "echo \"There's nothing to test\" && exit 1"
|
"test": "node scripts/check-i18n"
|
||||||
},
|
},
|
||||||
"commitlint": {
|
"commitlint": {
|
||||||
"extends": [
|
"extends": [
|
||||||
@ -73,7 +73,7 @@
|
|||||||
"short-unique-id": "^4.4.4",
|
"short-unique-id": "^4.4.4",
|
||||||
"spacetime": "^7.4.4",
|
"spacetime": "^7.4.4",
|
||||||
"terminal-link": "^2.1.1",
|
"terminal-link": "^2.1.1",
|
||||||
"yaml": "^1.10.2"
|
"yaml": "^2.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "^17.6.3",
|
"@commitlint/cli": "^17.6.3",
|
||||||
@ -84,6 +84,7 @@
|
|||||||
"eslint-plugin-unused-imports": "^2.0.0",
|
"eslint-plugin-unused-imports": "^2.0.0",
|
||||||
"husky": "^8.0.3",
|
"husky": "^8.0.3",
|
||||||
"lint-staged": "^13.2.2",
|
"lint-staged": "^13.2.2",
|
||||||
|
"markdown-table": "^3.0.3",
|
||||||
"nodemon": "^2.0.22"
|
"nodemon": "^2.0.22"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
|
@ -90,8 +90,8 @@ dependencies:
|
|||||||
specifier: ^2.1.1
|
specifier: ^2.1.1
|
||||||
version: 2.1.1
|
version: 2.1.1
|
||||||
yaml:
|
yaml:
|
||||||
specifier: ^1.10.2
|
specifier: ^2.3.2
|
||||||
version: 1.10.2
|
version: 2.3.2
|
||||||
|
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
bufferutil:
|
bufferutil:
|
||||||
@ -132,6 +132,9 @@ devDependencies:
|
|||||||
lint-staged:
|
lint-staged:
|
||||||
specifier: ^13.2.2
|
specifier: ^13.2.2
|
||||||
version: 13.2.3
|
version: 13.2.3
|
||||||
|
markdown-table:
|
||||||
|
specifier: ^3.0.3
|
||||||
|
version: 3.0.3
|
||||||
nodemon:
|
nodemon:
|
||||||
specifier: ^2.0.22
|
specifier: ^2.0.22
|
||||||
version: 2.0.22
|
version: 2.0.22
|
||||||
@ -2557,7 +2560,7 @@ packages:
|
|||||||
object-inspect: 1.12.3
|
object-inspect: 1.12.3
|
||||||
pidtree: 0.6.0
|
pidtree: 0.6.0
|
||||||
string-argv: 0.3.2
|
string-argv: 0.3.2
|
||||||
yaml: 2.3.1
|
yaml: 2.3.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- enquirer
|
- enquirer
|
||||||
- supports-color
|
- supports-color
|
||||||
@ -2707,6 +2710,10 @@ packages:
|
|||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/markdown-table@3.0.3:
|
||||||
|
resolution: {integrity: sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/marked@4.3.0:
|
/marked@4.3.0:
|
||||||
resolution: {integrity: sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==}
|
resolution: {integrity: sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==}
|
||||||
engines: {node: '>= 12'}
|
engines: {node: '>= 12'}
|
||||||
@ -4137,15 +4144,9 @@ packages:
|
|||||||
/yallist@4.0.0:
|
/yallist@4.0.0:
|
||||||
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
|
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
|
||||||
|
|
||||||
/yaml@1.10.2:
|
/yaml@2.3.2:
|
||||||
resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==}
|
resolution: {integrity: sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==}
|
||||||
engines: {node: '>= 6'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/yaml@2.3.1:
|
|
||||||
resolution: {integrity: sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==}
|
|
||||||
engines: {node: '>= 14'}
|
engines: {node: '>= 14'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/yargs-parser@18.1.3:
|
/yargs-parser@18.1.3:
|
||||||
resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==}
|
resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==}
|
||||||
|
222
scripts/check-i18n.js
Normal file
222
scripts/check-i18n.js
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/* eslint-disable no-console */
|
||||||
|
const fs = require('fs');
|
||||||
|
const {
|
||||||
|
Composer,
|
||||||
|
LineCounter,
|
||||||
|
Parser,
|
||||||
|
} = require('yaml');
|
||||||
|
|
||||||
|
function resolve(ast, key) {
|
||||||
|
return key
|
||||||
|
.split('.')
|
||||||
|
.reduce(
|
||||||
|
(acc, part) => acc.value.items.find(item => item.key.source === part),
|
||||||
|
ast,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const errors = [];
|
||||||
|
const locales = [];
|
||||||
|
const files = fs.readdirSync('./src/i18n').filter(file => file.endsWith('.yml'));
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
const locale = file.substring(0, file.length - 4);
|
||||||
|
locales.push(locale);
|
||||||
|
const content = fs.readFileSync(`./src/i18n/${file}`, 'utf8');
|
||||||
|
const lineCounter = new LineCounter();
|
||||||
|
const parser = new Parser(lineCounter.addNewLine);
|
||||||
|
const tokenGenerator = parser.parse(content);
|
||||||
|
// Unfortunately needs to be parsed twice because `Generator<Token>` is single-use?
|
||||||
|
// Might as well use the simpler YAML.parse() instead of the Composer but I've already written this.
|
||||||
|
const [ast] = Array.from(parser.parse(content));
|
||||||
|
const docs = new Composer().compose(tokenGenerator);
|
||||||
|
const [doc] = Array.from(docs, doc => doc.toJS());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message context menu commands
|
||||||
|
*/
|
||||||
|
|
||||||
|
for (const [key, command] of Object.entries(doc.commands?.message || {})) {
|
||||||
|
if (command.name?.length > 32) {
|
||||||
|
const searchKey = `commands.message.${key}.name`;
|
||||||
|
const {
|
||||||
|
col,
|
||||||
|
line,
|
||||||
|
} = lineCounter.linePos(resolve(ast, searchKey).value.offset);
|
||||||
|
errors.push({
|
||||||
|
col,
|
||||||
|
line,
|
||||||
|
locale,
|
||||||
|
message: `\`${searchKey}\` is too long (${command.name.length} > 32)`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chat input (slash) commands
|
||||||
|
*/
|
||||||
|
|
||||||
|
for (const [key, command] of Object.entries(doc.commands?.slash || {})) {
|
||||||
|
|
||||||
|
const regex = /^[-_\p{L}\p{N}\p{sc=Deva}\p{sc=Thai}]{1,32}$/u;
|
||||||
|
|
||||||
|
if (command.name) {
|
||||||
|
if (!regex.test(command.name)) {
|
||||||
|
const searchKey = `commands.slash.${key}.name`;
|
||||||
|
const {
|
||||||
|
col,
|
||||||
|
line,
|
||||||
|
} = lineCounter.linePos(resolve(ast, searchKey).value.offset);
|
||||||
|
errors.push({
|
||||||
|
col,
|
||||||
|
line,
|
||||||
|
locale,
|
||||||
|
message: `\`${searchKey}\` does not match the regex pattern \`${regex.toString()}\``,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.name !== command.name.toLocaleLowerCase()) {
|
||||||
|
const searchKey = `commands.slash.${key}.name`;
|
||||||
|
const {
|
||||||
|
col,
|
||||||
|
line,
|
||||||
|
} = lineCounter.linePos(resolve(ast, searchKey).value.offset);
|
||||||
|
errors.push({
|
||||||
|
col,
|
||||||
|
line,
|
||||||
|
locale,
|
||||||
|
message: `\`${searchKey}\` is not lowercase`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.description?.length > 100) {
|
||||||
|
const searchKey = `commands.slash.${key}.description`;
|
||||||
|
const {
|
||||||
|
col,
|
||||||
|
line,
|
||||||
|
} = lineCounter.linePos(resolve(ast, searchKey).value.offset);
|
||||||
|
errors.push({
|
||||||
|
col,
|
||||||
|
line,
|
||||||
|
locale,
|
||||||
|
message: `\`${searchKey}\` is too long (${command.description.length} > 100)`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [key2, option] of Object.entries(command.options || {})) {
|
||||||
|
if (option.name) {
|
||||||
|
if (!regex.test(option.name)) {
|
||||||
|
const searchKey = `commands.slash.${key}.options.${key2}.name`;
|
||||||
|
const {
|
||||||
|
col,
|
||||||
|
line,
|
||||||
|
} = lineCounter.linePos(resolve(ast, searchKey).value.offset);
|
||||||
|
errors.push({
|
||||||
|
col,
|
||||||
|
line,
|
||||||
|
locale,
|
||||||
|
message: `\`${searchKey}\` does not match the regex pattern "${regex.toString()}"`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (option.name !== option.name.toLocaleLowerCase()) {
|
||||||
|
const searchKey = `commands.slash.${key}.options.${key2}.name`;
|
||||||
|
const {
|
||||||
|
col,
|
||||||
|
line,
|
||||||
|
} = lineCounter.linePos(resolve(ast, searchKey).value.offset);
|
||||||
|
errors.push({
|
||||||
|
col,
|
||||||
|
line,
|
||||||
|
locale,
|
||||||
|
message: `\`${searchKey}\` is not lowercase`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (option.description?.length > 100) {
|
||||||
|
const searchKey = `commands.slash.${key}.options.${key2}.description`;
|
||||||
|
const {
|
||||||
|
col,
|
||||||
|
line,
|
||||||
|
} = lineCounter.linePos(resolve(ast, searchKey).value.offset);
|
||||||
|
errors.push({
|
||||||
|
col,
|
||||||
|
line,
|
||||||
|
locale,
|
||||||
|
message: `\`${searchKey}\` is too long (${option.description.length} > 100)`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User context menu commands
|
||||||
|
*/
|
||||||
|
|
||||||
|
for (const [key, command] of Object.entries(doc.commands?.user || {})) {
|
||||||
|
if (command.name?.length > 32) {
|
||||||
|
const searchKey = `commands.user.${key}.name`;
|
||||||
|
const {
|
||||||
|
col,
|
||||||
|
line,
|
||||||
|
} = lineCounter.linePos(resolve(ast, searchKey).value.offset);
|
||||||
|
errors.push({
|
||||||
|
col,
|
||||||
|
line,
|
||||||
|
locale,
|
||||||
|
message: `\`${searchKey}\` is too long (${command.name.length} > 32)`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// @actions/core:
|
||||||
|
// https://github.blog/2022-05-09-supercharging-github-actions-with-job-summaries/
|
||||||
|
|
||||||
|
async function summarise() {
|
||||||
|
const { markdownTable } = await import('markdown-table');
|
||||||
|
const branch = process.env.GITHUB_SHA || 'main';
|
||||||
|
let summary = '# Test results\n\n';
|
||||||
|
summary +=
|
||||||
|
markdownTable([
|
||||||
|
['Locale', 'Result'],
|
||||||
|
...locales.map(locale => [
|
||||||
|
`${locale}`,
|
||||||
|
errors.filter(error => error.locale === locale).length === 0 ? '✅ Passed' : '❌ Failed',
|
||||||
|
]),
|
||||||
|
]) + '\n\n';
|
||||||
|
|
||||||
|
for (const locale of locales) {
|
||||||
|
// thanks copilot
|
||||||
|
console.log(`::group::${locale}`);
|
||||||
|
const localeErrors = errors.filter(error => error.locale === locale);
|
||||||
|
|
||||||
|
summary += `## \`${locale}\`\n\n`;
|
||||||
|
|
||||||
|
if (localeErrors.length > 0) {
|
||||||
|
for (const error of localeErrors) {
|
||||||
|
summary += `https://github.com/discord-tickets/bot/blob/${branch}/src/i18n/${locale}.yml#L${error.line}\n\n`;
|
||||||
|
console.log(`::error file=src/i18n/${locale}.yml,line=${error.line},col=${error.col}::${error.message}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
summary += 'No errors found\n\n';
|
||||||
|
console.log(`::notice file=src/i18n/${locale}.yml::No errors found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('::endgroup::');
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(process.env.GITHUB_STEP_SUMMARY || 'summary.md', summary);
|
||||||
|
|
||||||
|
if (errors.length > 0) {
|
||||||
|
console.error('Failed with %d errors', errors.length);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
summarise();
|
@ -54,7 +54,7 @@ commands:
|
|||||||
options:
|
options:
|
||||||
member:
|
member:
|
||||||
description: The member to add to the ticket
|
description: The member to add to the ticket
|
||||||
name: member
|
name: Member
|
||||||
ticket:
|
ticket:
|
||||||
description: The ticket to add the member to
|
description: The ticket to add the member to
|
||||||
name: ticket
|
name: ticket
|
||||||
|
Loading…
Reference in New Issue
Block a user