Require languages to be at least 75% translated
This commit is contained in:
@@ -19,30 +19,7 @@ import HttpBackend from 'i18next-http-backend';
|
||||
*
|
||||
* This is generated by the `i18n:gen-resources` script
|
||||
*/
|
||||
export const i18nResources = [
|
||||
'ar',
|
||||
'bn',
|
||||
'da',
|
||||
'de',
|
||||
'en',
|
||||
'es',
|
||||
'fr',
|
||||
'id',
|
||||
'it',
|
||||
'ja',
|
||||
'ko',
|
||||
'nb-NO',
|
||||
'nl',
|
||||
'pt',
|
||||
'ru',
|
||||
'sv',
|
||||
'th',
|
||||
'tr',
|
||||
'uk',
|
||||
'vi',
|
||||
'zh_Hans',
|
||||
'zh_Hant',
|
||||
] as const;
|
||||
export const i18nResources = ['en', 'es', 'pt', 'ru', 'tr', 'vi', 'zh_Hans', 'zh_Hant'] as const;
|
||||
|
||||
export type I18nResourceCode = (typeof i18nResources)[number];
|
||||
|
||||
|
||||
@@ -8,21 +8,55 @@
|
||||
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import {
|
||||
fetchWeblateLanguageStats,
|
||||
getWeblateLanguageStatsFor,
|
||||
meetsTranslatedPercentThreshold as meetsTranslatedPercentThresholdBase,
|
||||
} from './weblate/Weblate.utils.ts';
|
||||
import { WeblateLanguageStatistic } from './weblate/Weblate.types.ts';
|
||||
import { TRANSLATED_PERCENT_THRESHOLD } from './weblate/Weblate.constants.ts';
|
||||
|
||||
const outputFilePath = path.join(import.meta.dirname, '../../src/i18n/index.ts');
|
||||
const resourcesDirPath = path.join(import.meta.dirname, '../../public/locales');
|
||||
|
||||
const resourceFileNames = fs.readdirSync(resourcesDirPath);
|
||||
const extractLanguageCode = (resourceFileNames: string): string =>
|
||||
path.basename(resourceFileNames, path.extname(resourceFileNames));
|
||||
|
||||
const resourceNames = resourceFileNames.map((resourceFileName) =>
|
||||
path.basename(resourceFileName, path.extname(resourceFileName)),
|
||||
);
|
||||
const meetsTranslatedPercentThreshold = (
|
||||
resourceFileName: string,
|
||||
weblateLanguageStats: WeblateLanguageStatistic[],
|
||||
): boolean => {
|
||||
const code = extractLanguageCode(resourceFileName);
|
||||
const { name, translated_percent: translatedPercent } = getWeblateLanguageStatsFor(code, weblateLanguageStats);
|
||||
|
||||
const outputFileContent = fs.readFileSync(outputFilePath, 'utf-8');
|
||||
const meetsThreshold = meetsTranslatedPercentThresholdBase(translatedPercent);
|
||||
|
||||
const updatedFileContent = outputFileContent.replace(
|
||||
if (!meetsThreshold) {
|
||||
console.log(
|
||||
`Language "${name} (${code})" does not meet the required translation percentage threshold (${translatedPercent} < ${TRANSLATED_PERCENT_THRESHOLD})`,
|
||||
);
|
||||
}
|
||||
|
||||
return meetsThreshold;
|
||||
};
|
||||
|
||||
const generateResources = async () => {
|
||||
const weblateLanguageStats = await fetchWeblateLanguageStats();
|
||||
|
||||
const resourceFileNames = fs.readdirSync(resourcesDirPath);
|
||||
|
||||
const resourceNames = resourceFileNames
|
||||
.filter((resourceFileName) => meetsTranslatedPercentThreshold(resourceFileName, weblateLanguageStats))
|
||||
.map(extractLanguageCode);
|
||||
|
||||
const outputFileContent = fs.readFileSync(outputFilePath, 'utf-8');
|
||||
|
||||
const updatedFileContent = outputFileContent.replace(
|
||||
/(export const i18nResources = \[)[\s\S]*?(])/g,
|
||||
`$1'${resourceNames.join("', '")}'$2`,
|
||||
);
|
||||
);
|
||||
|
||||
fs.writeFileSync(outputFilePath, updatedFileContent);
|
||||
fs.writeFileSync(outputFilePath, updatedFileContent);
|
||||
};
|
||||
|
||||
generateResources();
|
||||
|
||||
@@ -18,9 +18,22 @@ import {
|
||||
UsernameByUserUrl,
|
||||
UserUrlsByTranslationUrl,
|
||||
WeblateChangeResult,
|
||||
WeblateLanguageStatistic,
|
||||
} from '../weblate/Weblate.types.ts';
|
||||
import { fetchWeblateChanges, getLanguageName, getUsername, validateWeblateDates } from '../weblate/Weblate.utils.ts';
|
||||
import { creditRelevantActions, TRANSLATION_CHANGELOG_YARG_OPTIONS_DEFAULT } from '../weblate/Weblate.constants.ts';
|
||||
import {
|
||||
fetchWeblateChanges,
|
||||
fetchWeblateLanguageStats,
|
||||
getLanguageName,
|
||||
getUsername,
|
||||
getWeblateLanguageStatsFor,
|
||||
meetsTranslatedPercentThreshold,
|
||||
validateWeblateDates,
|
||||
} from '../weblate/Weblate.utils.ts';
|
||||
import {
|
||||
creditRelevantActions,
|
||||
TRANSLATED_PERCENT_THRESHOLD,
|
||||
TRANSLATION_CHANGELOG_YARG_OPTIONS_DEFAULT,
|
||||
} from '../weblate/Weblate.constants.ts';
|
||||
|
||||
const extractContributionInfoFromChanges = (
|
||||
changes: WeblateChangeResult[],
|
||||
@@ -105,11 +118,34 @@ const getUserContributionByLanguage = (
|
||||
}),
|
||||
);
|
||||
|
||||
const doesLanguageOfChangeMeetTranslatePercentThreshold = (
|
||||
change: WeblateChangeResult,
|
||||
stats: WeblateLanguageStatistic[],
|
||||
): boolean => {
|
||||
// format: 'https://hosted.weblate.org/api/translations/suwayomi/suwayomi-webui/ar/'
|
||||
const code = change.translation.split('/').slice(-2)[0];
|
||||
const langaugeStats = getWeblateLanguageStatsFor(code, stats);
|
||||
|
||||
return meetsTranslatedPercentThreshold(langaugeStats.translated_percent);
|
||||
};
|
||||
|
||||
const getContributorsForRange = async (url: string, authToken: string): Promise<ContributionsByLanguage> => {
|
||||
const weblateChanges = await fetchWeblateChanges(url, authToken);
|
||||
const weblateLanguageStats = await fetchWeblateLanguageStats();
|
||||
|
||||
const validWeblateChanges = weblateChanges.filter((change) =>
|
||||
doesLanguageOfChangeMeetTranslatePercentThreshold(change, weblateLanguageStats),
|
||||
);
|
||||
|
||||
const invalidWeblateChangesAmount = Math.abs(validWeblateChanges.length - weblateChanges.length);
|
||||
if (invalidWeblateChangesAmount > 0) {
|
||||
console.log(
|
||||
`Filtered out ${invalidWeblateChangesAmount} invalid change(s) due languages not meeting the translated percent threshold (${TRANSLATED_PERCENT_THRESHOLD})`,
|
||||
);
|
||||
}
|
||||
|
||||
const { userUrlsByTranslationUrl, actionsByTranslationUrlByUserUrl } =
|
||||
extractContributionInfoFromChanges(weblateChanges);
|
||||
extractContributionInfoFromChanges(validWeblateChanges);
|
||||
|
||||
const usernameByUserUrl = await getUsernameByUserUrlMap(userUrlsByTranslationUrl, authToken);
|
||||
const languageNameByTranslationUrl = await getLanguageNameByTranslationUrl(userUrlsByTranslationUrl, authToken);
|
||||
|
||||
@@ -21,3 +21,5 @@ export const TRANSLATION_CHANGELOG_YARG_OPTIONS_DEFAULT = {
|
||||
requiredContributionCount: 10,
|
||||
keepKnownContributors: true,
|
||||
};
|
||||
|
||||
export const TRANSLATED_PERCENT_THRESHOLD = 75;
|
||||
|
||||
@@ -55,7 +55,9 @@ export interface WeblateUserPayload {
|
||||
export interface WeblateLanguagePayload {
|
||||
language: {
|
||||
name: string;
|
||||
code: string;
|
||||
};
|
||||
translated_percent: number;
|
||||
}
|
||||
|
||||
export type Username = string;
|
||||
@@ -78,3 +80,9 @@ export interface Contributor {
|
||||
}
|
||||
|
||||
export type ContributionsByLanguage = Record<LanguageName, Contributor[]>;
|
||||
|
||||
export interface WeblateLanguageStatistic {
|
||||
translated_percent: number;
|
||||
code: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
@@ -10,8 +10,11 @@ import {
|
||||
WeblateChangePayload,
|
||||
WeblateChangeResult,
|
||||
WeblateLanguagePayload,
|
||||
WeblateLanguageStatistic,
|
||||
WeblateUserPayload,
|
||||
} from '@/../tools/scripts/weblate/Weblate.types.ts';
|
||||
import tokens from '../tokens.json';
|
||||
import { TRANSLATED_PERCENT_THRESHOLD } from '@/../tools/scripts/weblate/Weblate.constants.ts';
|
||||
|
||||
export const fetchData = async <T = any>(url: string, authToken: string): Promise<T> => {
|
||||
const response = await fetch(url, {
|
||||
@@ -56,6 +59,12 @@ export const getLanguageName = async (url: string, authToken: string): Promise<s
|
||||
return languagePayload.language.name.replace(/\(([a-zA-Z]+) Han script\)/g, '($1)');
|
||||
};
|
||||
|
||||
export const fetchWeblateLanguageStats = async () =>
|
||||
fetchData(
|
||||
'https://hosted.weblate.org/api/components/suwayomi/suwayomi-webui/statistics/?format=json-flat',
|
||||
tokens.weblateToken,
|
||||
);
|
||||
|
||||
const dateRegex = /[0-9]{4}-[0-9]{2}-[0-9]{2}/g;
|
||||
const isValidIS08601Date = (date: string): boolean => !!date.match(dateRegex);
|
||||
export const validateWeblateDates = (afterDate: string, beforeDate: string) => {
|
||||
@@ -65,3 +74,23 @@ export const validateWeblateDates = (afterDate: string, beforeDate: string) => {
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const normalizeCode = (code: string): string => code.replace(/[-_]/g, '');
|
||||
|
||||
export const getWeblateLanguageStatsFor = (
|
||||
code: string,
|
||||
stats: WeblateLanguageStatistic[],
|
||||
): WeblateLanguageStatistic => {
|
||||
const langaugeStats = stats.find((stat) => normalizeCode(stat.code) === normalizeCode(code));
|
||||
|
||||
if (!langaugeStats) {
|
||||
throw new Error(`Weblate language stats for "${code} do not exist`);
|
||||
}
|
||||
|
||||
return langaugeStats;
|
||||
};
|
||||
|
||||
export const meetsTranslatedPercentThreshold = (
|
||||
translatedPercent: number,
|
||||
threshold: number = TRANSLATED_PERCENT_THRESHOLD,
|
||||
): boolean => translatedPercent >= threshold;
|
||||
|
||||
Reference in New Issue
Block a user