From 0a34ae01ba2c02238965623723b3c71b1d480875 Mon Sep 17 00:00:00 2001 From: schroda <50052685+schroda@users.noreply.github.com> Date: Mon, 17 Feb 2025 02:29:50 +0100 Subject: [PATCH] Require languages to be at least 75% translated --- src/i18n/index.ts | 25 +-------- tools/scripts/generatei18nResources.ts | 54 +++++++++++++++---- .../release/TranslationChangelog.utils.ts | 42 +++++++++++++-- tools/scripts/weblate/Weblate.constants.ts | 2 + tools/scripts/weblate/Weblate.types.ts | 8 +++ tools/scripts/weblate/Weblate.utils.ts | 29 ++++++++++ 6 files changed, 123 insertions(+), 37 deletions(-) diff --git a/src/i18n/index.ts b/src/i18n/index.ts index 514324e6..f6e0bf3e 100644 --- a/src/i18n/index.ts +++ b/src/i18n/index.ts @@ -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]; diff --git a/tools/scripts/generatei18nResources.ts b/tools/scripts/generatei18nResources.ts index c4ec4156..1fd84b23 100644 --- a/tools/scripts/generatei18nResources.ts +++ b/tools/scripts/generatei18nResources.ts @@ -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( - /(export const i18nResources = \[)[\s\S]*?(])/g, - `$1'${resourceNames.join("', '")}'$2`, -); + if (!meetsThreshold) { + console.log( + `Language "${name} (${code})" does not meet the required translation percentage threshold (${translatedPercent} < ${TRANSLATED_PERCENT_THRESHOLD})`, + ); + } -fs.writeFileSync(outputFilePath, updatedFileContent); + 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); +}; + +generateResources(); diff --git a/tools/scripts/release/TranslationChangelog.utils.ts b/tools/scripts/release/TranslationChangelog.utils.ts index 46086346..af4f04f9 100644 --- a/tools/scripts/release/TranslationChangelog.utils.ts +++ b/tools/scripts/release/TranslationChangelog.utils.ts @@ -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 => { 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); diff --git a/tools/scripts/weblate/Weblate.constants.ts b/tools/scripts/weblate/Weblate.constants.ts index 09955749..57f68b60 100644 --- a/tools/scripts/weblate/Weblate.constants.ts +++ b/tools/scripts/weblate/Weblate.constants.ts @@ -21,3 +21,5 @@ export const TRANSLATION_CHANGELOG_YARG_OPTIONS_DEFAULT = { requiredContributionCount: 10, keepKnownContributors: true, }; + +export const TRANSLATED_PERCENT_THRESHOLD = 75; diff --git a/tools/scripts/weblate/Weblate.types.ts b/tools/scripts/weblate/Weblate.types.ts index e0d31d2e..85f3338b 100644 --- a/tools/scripts/weblate/Weblate.types.ts +++ b/tools/scripts/weblate/Weblate.types.ts @@ -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; + +export interface WeblateLanguageStatistic { + translated_percent: number; + code: string; + name: string; +} diff --git a/tools/scripts/weblate/Weblate.utils.ts b/tools/scripts/weblate/Weblate.utils.ts index f8c10e50..e795ed02 100644 --- a/tools/scripts/weblate/Weblate.utils.ts +++ b/tools/scripts/weblate/Weblate.utils.ts @@ -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 (url: string, authToken: string): Promise => { const response = await fetch(url, { @@ -56,6 +59,12 @@ export const getLanguageName = async (url: string, authToken: string): Promise + 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;