scratch-l10n
Version:
Localization for the Scratch 3.0 components
143 lines (126 loc) • 4.99 kB
text/typescript
import locales, { localeMap } from '../../src/supported-locales.mjs'
import { poolMap } from './concurrent.mts'
import { ProgressLogger } from './progress-logger.mts'
import { TransifexStringsKeyValueJson } from './transifex-formats.mts'
import { txPull, txResources } from './transifex.mts'
import { filterInvalidTranslations, TransifexEditorString } from './validate.mts'
const CONCURRENCY_LIMIT = 36
const SOURCE_LOCALE = 'en' // TODO: don't hardcode this
/**
* @param resources - A list of Transifex resource names
* @param selectedLocales - A list of Scratch locale codes
* @returns The list of all resource/locale combinations to pull. The source locale is always excluded.
*/
const expandResourceFiles = (resources: string[], selectedLocales: string[]) => {
const files = []
for (const resource of resources) {
for (const locale of selectedLocales) {
if (locale === SOURCE_LOCALE) {
continue
}
files.push({ resource: resource, locale: locale })
}
}
return files
}
/**
* Pull and validate a single Transifex "file" (resource + locale).
* @param o - The options for pulling and validating a single file.
* @param o.allStrings - All pulled strings so far, used to get source strings for validation
* @param o.project - The Transifex project to pull from
* @param o.resource - The Transifex resource to pull from
* @param o.locale - The locale to pull
* @param o.mode - The mode to use when pulling (e.g., "reviewed")
* @returns A list of messages about errors encountered during validation, if any.
*/
async function pullAndValidateFile({
allStrings,
project,
resource,
locale,
mode,
}: {
allStrings: Record<string, Record<string, TransifexStringsKeyValueJson>>
project: string
resource: string
locale: string
mode?: string
}) {
const messages: string[] = []
const txLocale = localeMap[locale] || locale
const fileContent = await txPull<TransifexEditorString>(project, resource, txLocale, mode)
// if fileContent has message & description, we only want the message
const translations: TransifexStringsKeyValueJson = {}
for (const key of Object.keys(fileContent)) {
const tx = fileContent[key]
if (typeof tx === 'string') {
translations[key] = tx
} else {
translations[key] = tx.message
}
}
if (!(resource in allStrings)) {
allStrings[resource] = {}
}
allStrings[resource][locale] = translations
// may or may not be the same as `translations`
const sourceStrings = allStrings[resource][SOURCE_LOCALE]
// some of the validation checks may still be relevant even if locale === SOURCE_LOCALE
// console.log({ resource, locale, translations, sourceStrings })
messages.push(...filterInvalidTranslations(locale, translations, sourceStrings))
return messages
}
/**
* Pull one or more resource(s) from a transifex project and validate the strings.
* Return any error messages from the validation process along with all valid strings.
* @param o - The options for pulling, validating, and saving translations.
* @param o.project - The Transifex project to pull translations from.
* @param o.mode - The mode to use when pulling translations (e.g., "reviewed").
* @param o.resources - The resources within the project to pull translations for.
* @param o.selectedLocales - The locales to pull translations for. Defaults to all supported locales.
* @returns Translation strings and a list of messages about errors encountered during validation, if any.
*/
export async function pullAndValidateProject({
project,
resources,
mode,
selectedLocales,
}: {
project: string
mode?: string
resources?: string[]
selectedLocales?: string | string[]
}) {
const selectedResources = resources ?? (await txResources(project))
selectedLocales =
typeof selectedLocales === 'string' ? [selectedLocales] : (selectedLocales ?? Object.keys(locales))
const files = expandResourceFiles(selectedResources, selectedLocales)
const allStrings: Record<string, Record<string, TransifexStringsKeyValueJson>> = {}
const messages: string[] = []
const progress = new ProgressLogger(selectedResources.length + files.length)
const handleFile = async (resource: string, locale: string) => {
try {
const fileMessages = await pullAndValidateFile({
allStrings,
project,
resource,
locale,
mode,
})
for (const message of fileMessages) {
// `message` already contains locale and/or string info if appropriate
messages.push(`resource ${resource} / ${message}`)
}
} finally {
progress.increment()
}
}
// Ensure source locale is available for validation
await poolMap(selectedResources, CONCURRENCY_LIMIT, async resource => handleFile(resource, SOURCE_LOCALE))
// Non-source locales
await poolMap(files, CONCURRENCY_LIMIT, async ({ resource, locale }) => handleFile(resource, locale))
return {
allStrings,
messages,
}
}