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

Run uploads in submodules, create empty language files & upgrade all packages #13

Merged
merged 12 commits into from Aug 13, 2023
8,770 changes: 2,190 additions & 6,580 deletions package-lock.json

Large diffs are not rendered by default.

51 changes: 26 additions & 25 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,34 +9,35 @@
},
"type": "module",
"dependencies": {
"@actions/core": "^1.9.1",
"@crowdin/crowdin-api-client": "^1.18.0",
"adm-zip": "^0.5.9",
"axios": "^0.26.0",
"fs-extra": "^10.0.1",
"p-limit": "3.1.0"
"@actions/core": "^1.10.0",
"@crowdin/crowdin-api-client": "^1.24.0",
"adm-zip": "^0.5.10",
"axios": "^1.2.2",
"fs-extra": "^11.1.0"
},
"devDependencies": {
"@types/adm-zip": "^0.5.0",
"@types/fs-extra": "^9.0.13",
"@types/jest": "^27.4.0",
"@types/node": "^16.11.7",
"@typescript-eslint/eslint-plugin": "^5.28.0",
"@typescript-eslint/parser": "^5.28.0",
"@vercel/ncc": "^0.34.0",
"eslint": "^8.17.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jsdoc": "^39.3.2",
"@types/fs-extra": "^11.0.1",
"@types/jest": "^29.5.2",
"@types/mock-fs": "^4.13.1",
"@types/node": "^16.18.11",
"@typescript-eslint/eslint-plugin": "^5.48.2",
"@typescript-eslint/parser": "^5.48.2",
"@vercel/ncc": "^0.36.0",
"eslint": "^8.45.0",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-jsdoc": "^46.4.6",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-only-warn": "^1.0.3",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-promise": "^6.0.0",
"jest": "^27.5.1",
"nock": "^13.2.4",
"prettier": "^2.5.1",
"removeNPMAbsolutePaths": "^3.0.0",
"ts-jest": "^27.1.3",
"typescript": "^4.7.3"
"eslint-plugin-only-warn": "^1.1.0",
"eslint-plugin-prettier": "^5.0.0",
"eslint-plugin-promise": "^6.1.1",
"jest": "^29.5.0",
"mock-fs": "^5.2.0",
"nock": "^13.3.0",
"prettier": "^3.0.0",
"removeNPMAbsolutePaths": "^3.0.1",
"ts-jest": "^29.1.0",
"typescript": "^4.9.4"
}
}
114 changes: 39 additions & 75 deletions src/download.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,35 +19,15 @@ const { reportsApi, translationsApi, usersApi, projectsGroupsApi, sourceFilesApi
token: CROWDIN_PAT || ''
});

/**
* Clears a directory excluding the default language files.
*
* @param dirPath Directory to clear.
*/
async function emptyTranslationDir(dirPath: string): Promise<void> {
if (!(await FSE.pathExists(dirPath))) {
return;
}
for (const file of await FSE.readdir(dirPath)) {
if (file !== `${STRINGS.englishLanguage.locale}.ini`) {
await FSE.remove(`${dirPath}/${file}`);
}
}
}

/**
* Retrieves submodules with translatable files.
*
* @returns A list of submodules with translataable files.
*/
async function getSubmodules(): Promise<string[]> {
const submodules = [];
for (const line of exec('git config --file .gitmodules --get-regexp path').trimEnd().split('\n')) {
const submodulePath = line.split(' ')[1];
if (
(await FSE.pathExists(`${submodulePath}/data/locale/en-US.ini`)) ||
(submodulePath === 'plugins/enc-amf' && FSE.pathExists('plugins/enc-amf/resources/locale/en-US.ini'))
) {
if (await FSE.pathExists(`${submodulePath}/data/locale/en-US.ini`)) {
submodules.push(submodulePath.split('/')[1]);
}
}
Expand All @@ -56,26 +36,32 @@ async function getSubmodules(): Promise<string[]> {

/**
* Remove all translations to prevent keeping dropped languages.
This conversation was marked as resolved.
Show resolved Hide resolved
* @param sourceFilePaths List of soure file export paths.
*/
export async function removePreviousTranslations(): Promise<void> {
await Promise.all([
emptyTranslationDir('UI/data/locale'),
emptyTranslationDir('plugins/enc-amf/resources/locale'),
emptyTranslationDir('plugins/mac-virtualcam/src/obs-plugin/data/locale')
]);
export async function removePreviousTranslations(sourceFilePaths: string[]): Promise<void> {
let pathsToClear: string[] = ['UI/data/locale', 'plugins/mac-virtualcam/src/obs-plugin/data/locale'];
for (const pluginRootDir of ['plugins', 'UI/frontend-plugins']) {
if (!(await FSE.pathExists(pluginRootDir))) {
continue;
}
for (const pluginDir of await FSE.readdir(pluginRootDir)) {
const pluginLocalePath = `${pluginRootDir}/${pluginDir}/data/locale`;
if ((await FSE.pathExists(pluginLocalePath)) && (await FSE.lstat(pluginLocalePath)).isDirectory()) {
await emptyTranslationDir(pluginLocalePath);
pathsToClear.push(`${pluginRootDir}/${pluginDir}/data/locale`);
}
}
for (const path of pathsToClear) {
if (!(await FSE.pathExists(path))) {
continue;
}
for (const file of await FSE.readdir(path)) {
if (file !== `${STRINGS.englishLanguage.locale}.ini` && sourceFilePaths.includes(path)) {
await FSE.remove(`${path}/${file}`);
}
}
}
}

/**
* Finds submodules uneven with the main repository and checkouts `master` branch.
*
* @param submodules A list of submodules.
* @returns List of detached submodules in the repository.
*/
Expand All @@ -94,7 +80,6 @@ function getDetachedSubmodules(submodules: string[]): string[] {

/**
* Mappes file ids to their export paths.
*
* @returns File ids mapped to their export path.
This conversation was marked as resolved.
Show resolved Hide resolved
*/
export async function getFilePaths(): Promise<Map<number, string>> {
Expand All @@ -109,7 +94,6 @@ export async function getFilePaths(): Promise<Map<number, string>> {

/**
* Maps source file ids to source string keys and source text.
*
* @param filePaths File ids mapped to their export path.
* @returns Source file ids mapped to source string keys and source text.
*/
Expand Down Expand Up @@ -142,7 +126,6 @@ export async function getSourceFiles(filePaths: Map<number, string>): Promise<Ma

/**
* Build the final string to be saved to the AUTHORS file.
*
* @param gitContributors Output of getGitContributors()
* @param translators Output of getTranslators()
*/
Expand All @@ -152,7 +135,6 @@ async function generateAuthors(gitContributors: string, translators: string): Pr

/**
* Uses `git shortlog` to get a list Git contributors.
*
* @returns List of contributors, with heading
*/
function getGitContributors(): string {
Expand All @@ -166,11 +148,8 @@ function getGitContributors(): string {
return output;
}

const requestLimit = PLIMIT(10);

/**
* Gets all translators from the Crowdin project.
*
* @param targetLanguageIds List of project target language ids.
* @returns List of translators, with heading.
*/
Expand All @@ -184,36 +163,27 @@ export async function getTranslators(targetLanguageIds: string[]): Promise<strin
blockedUsers.push(blockedUser.id);
}

const requests = [];
const topMembers = new Map<string, string[]>();
for (const languageId of targetLanguageIds) {
requests.push(
requestLimit(() =>
(async () => {
const { status: reportStatus, identifier: reportId } = (
await reportsApi.generateReport(PROJECT_ID, {
name: 'top-members',
schema: {
unit: 'words',
format: 'json',
dateFrom: '2014-01-01T00:00:00+00:00',
dateTo: '2030-01-01T00:00:00+00:00',
languageId
}
})
).data;
let finished = reportStatus === 'finished';
while (!finished) {
await wait(3000);
finished = (await reportsApi.checkReportStatus(PROJECT_ID, reportId)).data.status === 'finished';
}
return (await AXIOS.get((await reportsApi.downloadReport(PROJECT_ID, reportId)).data.url)).data;
})()
)
);
}
const { status: reportStatus, identifier: reportId } = (
await reportsApi.generateReport(PROJECT_ID, {
name: 'top-members',
schema: {
unit: 'words',
format: 'json',
dateFrom: '2014-01-01T00:00:00+00:00',
dateTo: new Date().getFullYear() + '-12-31T23:59:59+00:00',
languageId
}
})
).data;
let finished = reportStatus === 'finished';
while (!finished) {
await wait(1000);
finished = (await reportsApi.checkReportStatus(PROJECT_ID, reportId)).data.status === 'finished';
}
let reportData = (await AXIOS.get((await reportsApi.downloadReport(PROJECT_ID, reportId)).data.url)).data;

const topMembers = new Map<string, string[]>();
for (const reportData of await Promise.all(requests)) {
if (!('data' in reportData)) {
continue;
}
Expand Down Expand Up @@ -249,7 +219,6 @@ export async function getTranslators(targetLanguageIds: string[]): Promise<strin

/**
* Builds the Crowdin project.
*
* @returns The build id.
*/
export async function buildTranslations(): Promise<number> {
Expand All @@ -266,7 +235,6 @@ export async function buildTranslations(): Promise<number> {

/**
* Executes multiple language-related operations.
*
* @returns An object containing the properties `targetLanguageIds` and `languageCodeMap`.
*/
export async function getLanguages(): Promise<{
Expand All @@ -286,7 +254,6 @@ export async function getLanguages(): Promise<{

/**
* Downloads the build, trims the translation files and moves them to their directories.
*
* @param buildId Build id.
* @param sourceFiles Source files mapped to source string keys and their source text.
* @param filePaths File ids mapped to their export path.
Expand Down Expand Up @@ -354,7 +321,7 @@ export async function processBuild(
}
}
if (translationContent.length === 0) {
continue;
translationContent = '#';
}
} else {
translationContent = `${fileContent}\n`;
Expand All @@ -377,7 +344,6 @@ export async function processBuild(

/**
* Updates `com.obsproject.Studio.desktop` file with translations.
*
* @param languageFiles Locales mapped to their desktop file translations.
*/
export async function createDesktopFile(languageFiles: Map<string, Map<string, string>>): Promise<void> {
Expand All @@ -403,7 +369,6 @@ export async function createDesktopFile(languageFiles: Map<string, Map<string, s

/**
* Updates `locale.ini` file with languages.
*
* @param languageList Locales mapped to their locale language name.
* @param languageCodeMap Locales mapped to their language id.
*/
Expand Down Expand Up @@ -442,7 +407,6 @@ export async function createLocaleFile(languageList: Map<string, string>, langua
}
/**
* Pushes all changes to the submodules and the main repository.
*
* @param detachedSubmodules List of detached submodules in the repository.
* @param submodules A list of submodules.
*/
Expand All @@ -458,7 +422,7 @@ function pushChanges(detachedSubmodules: string[], submodules: string[]): void {
process.chdir('../..');
continue;
}
exec(`git add '${submodule === 'enc-amf' ? 'resources/locale/*-*.ini' : 'data/locale/*-*.ini'}'`);
exec('git add data/locale/*-*.ini');
exec(`git commit -m '${STRINGS.git.commitTitle}'`);
exec('git push');
process.chdir('../..');
Expand Down Expand Up @@ -495,7 +459,7 @@ function pushChanges(detachedSubmodules: string[], submodules: string[]): void {
return;
}
try {
await removePreviousTranslations();
await removePreviousTranslations(Array.from((await getFilePaths()).values()));
const submodules = await getSubmodules();
const results = [];
results[0] = await Promise.all([getDetachedSubmodules(submodules), getFilePaths(), buildTranslations(), getLanguages()]);
Expand Down
2 changes: 1 addition & 1 deletion src/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const strings = {
commitTitle: 'Update translations from Crowdin'
},
authors: {
header: 'Original Author: Hugh Bailey ("Jim")\n\nContributors are sorted by their amount of commits / translated words.\n\n',
header: 'Original Author: Lain Bailey\n\nContributors are sorted by their amount of commits / translated words.\n\n',
contributors: 'Contributors',
translators: 'Translators',
fileName: 'AUTHORS'
Expand Down
Loading