Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes & Improvements to i18n linting #16

Merged
merged 6 commits into from
Feb 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion .github/workflows/i18n_check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,16 @@
# Additionally prevents changes to files other than en_EN.json apart from RiotRobot automations
name: i18n Check
on:
workflow_call: {}
workflow_call:
inputs:
hardcoded-words:
type: string
required: false
description: "Hardcoded words to disallow, e.g. 'Element'."
allowed-hardcoded-keys:
type: string
required: false
description: "i18n keys which ignore the forbidden word check."
jobs:
check:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -37,5 +46,8 @@ jobs:
run: "yarn install --frozen-lockfile"

- run: yarn run i18n
env:
HARDCODED_WORDS: ${{ inputs.hardcoded-words }}
ALLOWED_HARDCODED_KEYS: ${{ inputs.allowed-hardcoded-keys }}

- run: git diff --exit-code
60 changes: 53 additions & 7 deletions scripts/lint-i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,32 +17,78 @@ limitations under the License.
/**
* Applies the following lint rules to the src/i18n/strings/en_EN.json file:
* + ensures the translation key is not equal to its value
* + ensures the translation key contains only alphanumerics and underscores
* + ensures the translation key contains only alphanumerics and underscores (temporarily allows @ and . for compatibility)
* + ensures no forbidden hardcoded words are found (specified new line delimited in environment variable HARDCODED_WORDS)
* unless they are explicitly allowed (keys specified new line delimited in environment variable ALLOWED_HARDCODED_KEYS)
*
* Usage: node scripts/lint-i18n.js
*/

import { getTranslations, isPluralisedTranslation } from "./common";
import { KEY_SEPARATOR, Translation, Translations } from "../src";

const hardcodedWords = process.env.HARDCODED_WORDS?.toLowerCase().split("\n").map(k => k.trim()) ?? [];
const allowedHardcodedKeys = process.env.ALLOWED_HARDCODED_KEYS?.split("\n").map(k => k.trim()) ?? [];

const input = getTranslations();

const filtered = Object.keys(input).filter(key => {
const value = input[key];
function nonNullable<T>(value: T): value is NonNullable<T> {
return value !== null && value !== undefined;
}

function expandTranslations(translation: Translation): string[] {
if (isPluralisedTranslation(translation)) {
return [translation.one, translation.other].filter(nonNullable)
} else {
return [translation];
}
}

function lintTranslation(keys: string[], value: Translation): boolean {
const key = keys[keys.length - 1];
const fullKey = keys.join(KEY_SEPARATOR);

// Check for invalid characters in the translation key
if (!!key.replace(/[a-z0-9_]+/g, "")) {
console.log(`"${key}": key contains invalid characters`);
if (!!key.replace(/[a-z0-9@_.]+/gi, "")) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this get a comment please 😇

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment at the top of the file describes the key validity rules

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah okay, thanks

console.log(`"${fullKey}": key contains invalid characters`);
return true;
}

// Check that the translated string does not match the key.
if (key === input[key] || (isPluralisedTranslation(value) && (key === value.other || key === value.one))) {
console.log(`"${key}": key matches value`);
console.log(`"${fullKey}": key matches value`);
return true;
}

if (hardcodedWords.length > 0) {
const words = expandTranslations(value).join(" ").toLowerCase().split(" ");
if (!allowedHardcodedKeys.includes(fullKey) && hardcodedWords.some(word => words.includes(word))) {
console.log(`"${fullKey}": contains forbidden hardcoded word`);
return true;
}
}

return false;
});
}

function traverseTranslations(translations: Translations, keys: string[] = []): string[] {
const filtered: string[] = [];
Object.keys(translations).forEach(key => {
const value = translations[key];

if (typeof value === "object" && !isPluralisedTranslation(value)) {
filtered.push(...traverseTranslations(value, [...keys, key]));
return;
}

if (lintTranslation([...keys, key], value)) {
filtered.push(key);
}
});
return filtered;
}

const filtered = traverseTranslations(input);

if (filtered.length > 0) {
console.log(`${filtered.length} invalid translation keys`);
Expand Down
Loading