chore: remove crowdin scripts (#49374)

This commit is contained in:
Naomi Carrigan 2023-02-13 23:53:38 -08:00 committed by GitHub
parent b5fcc389f0
commit 26367a17c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 0 additions and 1425 deletions

1
.github/labeler.yml vendored
View File

@ -23,4 +23,3 @@
- client/i18n/**/*
- config/crowdin/**/*
- config/i18n/**/*
- tools/crowdin/**/*

388
package-lock.json generated
View File

@ -19,7 +19,6 @@
"tools/challenge-editor/client",
"tools/challenge-helper-scripts",
"tools/challenge-parser",
"tools/crowdin",
"tools/scripts/build",
"tools/scripts/seed",
"tools/ui-components"
@ -702,38 +701,6 @@
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/@actions/core": {
"version": "1.10.0",
"license": "MIT",
"dependencies": {
"@actions/http-client": "^2.0.1",
"uuid": "^8.3.2"
}
},
"node_modules/@actions/core/node_modules/uuid": {
"version": "8.3.2",
"license": "MIT",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/@actions/github": {
"version": "5.1.1",
"license": "MIT",
"dependencies": {
"@actions/http-client": "^2.0.1",
"@octokit/core": "^3.6.0",
"@octokit/plugin-paginate-rest": "^2.17.0",
"@octokit/plugin-rest-endpoint-methods": "^5.13.0"
}
},
"node_modules/@actions/http-client": {
"version": "2.0.1",
"license": "MIT",
"dependencies": {
"tunnel": "^0.0.6"
}
},
"node_modules/@adobe/css-tools": {
"version": "4.0.1",
"license": "MIT"
@ -3272,10 +3239,6 @@
"resolved": "client",
"link": true
},
"node_modules/@freecodecamp/crowdin": {
"resolved": "tools/crowdin",
"link": true
},
"node_modules/@freecodecamp/curriculum": {
"resolved": "curriculum",
"link": true
@ -4946,111 +4909,6 @@
"node": ">=10"
}
},
"node_modules/@octokit/auth-token": {
"version": "2.5.0",
"license": "MIT",
"dependencies": {
"@octokit/types": "^6.0.3"
}
},
"node_modules/@octokit/core": {
"version": "3.6.0",
"license": "MIT",
"dependencies": {
"@octokit/auth-token": "^2.4.4",
"@octokit/graphql": "^4.5.8",
"@octokit/request": "^5.6.3",
"@octokit/request-error": "^2.0.5",
"@octokit/types": "^6.0.3",
"before-after-hook": "^2.2.0",
"universal-user-agent": "^6.0.0"
}
},
"node_modules/@octokit/endpoint": {
"version": "6.0.12",
"license": "MIT",
"dependencies": {
"@octokit/types": "^6.0.3",
"is-plain-object": "^5.0.0",
"universal-user-agent": "^6.0.0"
}
},
"node_modules/@octokit/endpoint/node_modules/is-plain-object": {
"version": "5.0.0",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/@octokit/graphql": {
"version": "4.8.0",
"license": "MIT",
"dependencies": {
"@octokit/request": "^5.6.0",
"@octokit/types": "^6.0.3",
"universal-user-agent": "^6.0.0"
}
},
"node_modules/@octokit/openapi-types": {
"version": "11.2.0",
"license": "MIT"
},
"node_modules/@octokit/plugin-paginate-rest": {
"version": "2.17.0",
"license": "MIT",
"dependencies": {
"@octokit/types": "^6.34.0"
},
"peerDependencies": {
"@octokit/core": ">=2"
}
},
"node_modules/@octokit/plugin-rest-endpoint-methods": {
"version": "5.13.0",
"license": "MIT",
"dependencies": {
"@octokit/types": "^6.34.0",
"deprecation": "^2.3.1"
},
"peerDependencies": {
"@octokit/core": ">=3"
}
},
"node_modules/@octokit/request": {
"version": "5.6.3",
"license": "MIT",
"dependencies": {
"@octokit/endpoint": "^6.0.1",
"@octokit/request-error": "^2.1.0",
"@octokit/types": "^6.16.1",
"is-plain-object": "^5.0.0",
"node-fetch": "^2.6.7",
"universal-user-agent": "^6.0.0"
}
},
"node_modules/@octokit/request-error": {
"version": "2.1.0",
"license": "MIT",
"dependencies": {
"@octokit/types": "^6.0.3",
"deprecation": "^2.0.0",
"once": "^1.4.0"
}
},
"node_modules/@octokit/request/node_modules/is-plain-object": {
"version": "5.0.0",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/@octokit/types": {
"version": "6.34.0",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^11.2.0"
}
},
"node_modules/@pmmmwh/react-refresh-webpack-plugin": {
"version": "0.4.3",
"license": "MIT",
@ -17982,10 +17840,6 @@
"version": "2.4.3",
"license": "MIT"
},
"node_modules/before-after-hook": {
"version": "2.2.2",
"license": "Apache-2.0"
},
"node_modules/better-opn": {
"version": "2.1.1",
"license": "MIT",
@ -22030,10 +21884,6 @@
"node": ">= 0.6"
}
},
"node_modules/deprecation": {
"version": "2.3.1",
"license": "ISC"
},
"node_modules/deps-sort": {
"version": "2.0.1",
"license": "MIT",
@ -38651,13 +38501,6 @@
"node": ">=0.10.0"
}
},
"node_modules/node-opencc": {
"version": "2.0.1",
"license": "MIT",
"engines": {
"node": ">= 7.6.0"
}
},
"node_modules/node-releases": {
"version": "2.0.6",
"license": "MIT"
@ -40516,14 +40359,6 @@
"which": "bin/which"
}
},
"node_modules/path": {
"version": "0.12.7",
"license": "MIT",
"dependencies": {
"process": "^0.11.1",
"util": "^0.10.3"
}
},
"node_modules/path-browserify": {
"version": "1.0.1",
"license": "MIT"
@ -40581,17 +40416,6 @@
"node": ">=8"
}
},
"node_modules/path/node_modules/inherits": {
"version": "2.0.3",
"license": "ISC"
},
"node_modules/path/node_modules/util": {
"version": "0.10.4",
"license": "MIT",
"dependencies": {
"inherits": "2.0.3"
}
},
"node_modules/pathval": {
"version": "1.1.1",
"license": "MIT",
@ -50289,13 +50113,6 @@
"integrity": "sha512-JVa5ijo+j/sOoHGjw0sxw734b1LhBkQ3bvUGNdxnVXDCX81Yx7TFgnZygxrIIWn23hbfTaMYLwRmAxFyDuFmIw==",
"dev": true
},
"node_modules/tunnel": {
"version": "0.0.6",
"license": "MIT",
"engines": {
"node": ">=0.6.11 <=0.7.0 || >=0.7.3"
}
},
"node_modules/tunnel-agent": {
"version": "0.6.0",
"license": "Apache-2.0",
@ -50841,10 +50658,6 @@
"url": "https://opencollective.com/unified"
}
},
"node_modules/universal-user-agent": {
"version": "6.0.0",
"license": "ISC"
},
"node_modules/universalify": {
"version": "2.0.0",
"license": "MIT",
@ -54282,38 +54095,6 @@
"unist-util-stringify-position": "^1.1.1"
}
},
"tools/crowdin": {
"name": "@freecodecamp/crowdin",
"version": "0.0.1",
"license": "BSD-3-Clause",
"dependencies": {
"@actions/core": "1.10.0",
"@actions/github": "5.1.1",
"dotenv": "16.0.3",
"fs-extra": "10.0.0",
"gray-matter": "4.0.3",
"node-fetch": "2.6.9",
"node-opencc": "2.0.1",
"path": "0.12.7",
"readdirp": "3.6.0"
},
"engines": {
"node": ">=16",
"npm": ">=8"
}
},
"tools/crowdin/node_modules/fs-extra": {
"version": "10.0.0",
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"engines": {
"node": ">=12"
}
},
"tools/scripts/build": {
"name": "@freecodecamp/scripts-build",
"version": "0.0.1",
@ -54447,33 +54228,6 @@
}
},
"dependencies": {
"@actions/core": {
"version": "1.10.0",
"requires": {
"@actions/http-client": "^2.0.1",
"uuid": "^8.3.2"
},
"dependencies": {
"uuid": {
"version": "8.3.2"
}
}
},
"@actions/github": {
"version": "5.1.1",
"requires": {
"@actions/http-client": "^2.0.1",
"@octokit/core": "^3.6.0",
"@octokit/plugin-paginate-rest": "^2.17.0",
"@octokit/plugin-rest-endpoint-methods": "^5.13.0"
}
},
"@actions/http-client": {
"version": "2.0.1",
"requires": {
"tunnel": "^0.0.6"
}
},
"@adobe/css-tools": {
"version": "4.0.1"
},
@ -56515,30 +56269,6 @@
}
}
},
"@freecodecamp/crowdin": {
"version": "file:tools/crowdin",
"requires": {
"@actions/core": "1.10.0",
"@actions/github": "5.1.1",
"dotenv": "16.0.3",
"fs-extra": "10.0.0",
"gray-matter": "4.0.3",
"node-fetch": "2.6.9",
"node-opencc": "2.0.1",
"path": "0.12.7",
"readdirp": "3.6.0"
},
"dependencies": {
"fs-extra": {
"version": "10.0.0",
"requires": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
}
}
}
},
"@freecodecamp/curriculum": {
"version": "file:curriculum",
"requires": {
@ -57801,91 +57531,6 @@
}
}
},
"@octokit/auth-token": {
"version": "2.5.0",
"requires": {
"@octokit/types": "^6.0.3"
}
},
"@octokit/core": {
"version": "3.6.0",
"requires": {
"@octokit/auth-token": "^2.4.4",
"@octokit/graphql": "^4.5.8",
"@octokit/request": "^5.6.3",
"@octokit/request-error": "^2.0.5",
"@octokit/types": "^6.0.3",
"before-after-hook": "^2.2.0",
"universal-user-agent": "^6.0.0"
}
},
"@octokit/endpoint": {
"version": "6.0.12",
"requires": {
"@octokit/types": "^6.0.3",
"is-plain-object": "^5.0.0",
"universal-user-agent": "^6.0.0"
},
"dependencies": {
"is-plain-object": {
"version": "5.0.0"
}
}
},
"@octokit/graphql": {
"version": "4.8.0",
"requires": {
"@octokit/request": "^5.6.0",
"@octokit/types": "^6.0.3",
"universal-user-agent": "^6.0.0"
}
},
"@octokit/openapi-types": {
"version": "11.2.0"
},
"@octokit/plugin-paginate-rest": {
"version": "2.17.0",
"requires": {
"@octokit/types": "^6.34.0"
}
},
"@octokit/plugin-rest-endpoint-methods": {
"version": "5.13.0",
"requires": {
"@octokit/types": "^6.34.0",
"deprecation": "^2.3.1"
}
},
"@octokit/request": {
"version": "5.6.3",
"requires": {
"@octokit/endpoint": "^6.0.1",
"@octokit/request-error": "^2.1.0",
"@octokit/types": "^6.16.1",
"is-plain-object": "^5.0.0",
"node-fetch": "^2.6.7",
"universal-user-agent": "^6.0.0"
},
"dependencies": {
"is-plain-object": {
"version": "5.0.0"
}
}
},
"@octokit/request-error": {
"version": "2.1.0",
"requires": {
"@octokit/types": "^6.0.3",
"deprecation": "^2.0.0",
"once": "^1.4.0"
}
},
"@octokit/types": {
"version": "6.34.0",
"requires": {
"@octokit/openapi-types": "^11.2.0"
}
},
"@pmmmwh/react-refresh-webpack-plugin": {
"version": "0.4.3",
"requires": {
@ -67305,9 +66950,6 @@
"bcryptjs": {
"version": "2.4.3"
},
"before-after-hook": {
"version": "2.2.2"
},
"better-opn": {
"version": "2.1.1",
"requires": {
@ -70066,9 +69708,6 @@
"depd": {
"version": "1.1.2"
},
"deprecation": {
"version": "2.3.1"
},
"deps-sort": {
"version": "2.0.1",
"requires": {
@ -81047,9 +80686,6 @@
"node-object-hash": {
"version": "2.3.10"
},
"node-opencc": {
"version": "2.0.1"
},
"node-releases": {
"version": "2.0.6"
},
@ -82247,24 +81883,6 @@
}
}
},
"path": {
"version": "0.12.7",
"requires": {
"process": "^0.11.1",
"util": "^0.10.3"
},
"dependencies": {
"inherits": {
"version": "2.0.3"
},
"util": {
"version": "0.10.4",
"requires": {
"inherits": "2.0.3"
}
}
}
},
"path-browserify": {
"version": "1.0.1"
},
@ -88378,9 +87996,6 @@
"integrity": "sha512-JVa5ijo+j/sOoHGjw0sxw734b1LhBkQ3bvUGNdxnVXDCX81Yx7TFgnZygxrIIWn23hbfTaMYLwRmAxFyDuFmIw==",
"dev": true
},
"tunnel": {
"version": "0.0.6"
},
"tunnel-agent": {
"version": "0.6.0",
"requires": {
@ -88719,9 +88334,6 @@
"unist-util-is": "^4.0.0"
}
},
"universal-user-agent": {
"version": "6.0.0"
},
"universalify": {
"version": "2.0.0"
},

View File

@ -28,7 +28,6 @@
"tools/challenge-editor/client",
"tools/challenge-helper-scripts",
"tools/challenge-parser",
"tools/crowdin",
"tools/scripts/build",
"tools/scripts/seed",
"tools/ui-components"

View File

@ -1,5 +0,0 @@
name: 'Translate Simplified Chinese to Traditional'
description: 'Converts Simplified Chinese characters to Traditional Chinese Characters'
runs:
using: 'node12'
main: './index.js'

View File

@ -1,41 +0,0 @@
/* eslint-disable import/no-unresolved */
const path = require('path');
const fs = require('fs-extra');
const opencc = require('node-opencc');
const getFiles = async (directory, fileList = []) => {
const files = await fs.readdir(directory);
for (const file of files) {
const fileStat = await fs.stat(path.join(directory, file));
if (fileStat.isDirectory()) {
fileList = await getFiles(path.join(directory, file), fileList);
} else {
fileList.push(path.join(directory, file));
}
}
return fileList;
};
(async () => {
console.info('Getting file list...');
const fileList = [];
const curriculum = await getFiles(
path.join(__dirname, '/../../../../curriculum/challenges/chinese')
);
fileList.push(...curriculum);
const client = await getFiles(
path.join(__dirname, '/../../../../client/i18n/locales/chinese')
);
fileList.push(...client);
for (const file of fileList) {
console.info(`Translating ${file}`);
const fileText = await fs.readFile(file, 'utf-8');
const translatedText = await opencc.simplifiedToTraditional(fileText);
await fs.outputFile(
file.replace('chinese', 'chinese-traditional'),
translatedText
);
}
})();

View File

@ -1,5 +0,0 @@
name: 'Hide Non-Translated Strings'
description: "Updates each file's non-translatable string to Hidden"
runs:
using: 'node12'
main: './index.js'

View File

@ -1,62 +0,0 @@
require('dotenv').config({ path: `${__dirname}/../../.env` });
const fs = require('fs');
const path = require('path');
const matter = require('gray-matter');
const { getFiles } = require('../../utils/files');
const { getStrings, updateFileString } = require('../../utils/strings');
const createChallengeTitleLookup = (
lookup,
{ fileId, path: crowdinFilePath }
) => {
const challengeFilePath = path.join(
__dirname,
'/../../../../',
crowdinFilePath
);
try {
const challengeContent = fs.readFileSync(challengeFilePath);
const {
data: { title: challengeTitle }
} = matter(challengeContent);
return {
...lookup,
[fileId]: {
crowdinFilePath,
challengeTitle
}
};
} catch (err) {
console.log(err.name);
console.log(err.message);
}
return lookup;
};
const hideNonTranslatedStrings = async projectId => {
console.log('hide non-translated strings...');
const crowdinFiles = await getFiles(projectId);
if (crowdinFiles && crowdinFiles.length) {
const challengeTitleLookup = crowdinFiles.reduce(
createChallengeTitleLookup,
{}
);
const crowdinStrings = await getStrings({ projectId });
if (crowdinStrings && crowdinStrings.length) {
for (let string of crowdinStrings) {
const { crowdinFilePath, challengeTitle } =
challengeTitleLookup[string.data.fileId];
await updateFileString({
projectId,
string,
challengeTitle,
crowdinFilePath
});
}
}
}
console.log('complete');
};
const projectId = process.env.CROWDIN_PROJECT_ID;
hideNonTranslatedStrings(projectId);

View File

@ -1,12 +0,0 @@
name: 'Hide Specific String'
description: 'Updates a specific string to be hidden'
runs:
using: 'node12'
main: './index.js'
inputs:
filename:
description: 'name of file with specific string to hide'
required: true
string-content:
description: 'text content of string to hide'
required: true

View File

@ -1,34 +0,0 @@
require('dotenv').config({ path: `${__dirname}/../../.env` });
const core = require('@actions/core');
const { getFiles } = require('../../utils/files');
const { getStrings, changeHiddenStatus } = require('../../utils/strings');
// eslint-disable-next-line import/no-unresolved
const filename = core.getInput('filename');
const stringContent = core.getInput('string-content');
const hideString = async (projectId, fileName, string) => {
const fileResponse = await getFiles(projectId);
const targetFile = fileResponse.find(el => el.path.endsWith(filename));
if (!targetFile) {
core.setFailed(`${fileName} was not found.`);
return;
}
const stringResponse = await getStrings({
projectId,
fileId: targetFile.fileId
});
const targetString = stringResponse.find(el => el.data.text === string);
if (!targetString) {
core.setFailed(`${string} was not found.`);
return;
}
await changeHiddenStatus(projectId, targetString.data.id, true);
console.log('string hidden!');
};
const projectId = process.env.CROWDIN_PROJECT_ID;
hideString(projectId, filename, stringContent);

View File

@ -1,37 +0,0 @@
name: 'Create Crowdin PRs'
description: 'Creates a PR by camperbot for Crowdin translation downloads'
runs:
using: 'node12'
main: './index.js'
inputs:
github-token:
description: 'PAT with write access to create PRs'
required: true
branch:
description: 'Branch name to which commits are made'
required: true
owner-repo:
description: 'owner and repo name specified as ownerName/repoName'
required: true
base:
description: 'base branch name'
default: 'main'
required: true
title:
description: 'PR title'
required: true
body:
description: 'PR body text'
required: true
labels:
description: 'PR labels'
required: false
reviewers:
description: 'Requested PR reviewers'
required: false
team_reviewers:
# Note that this should be a slug, not a full tag
# So a requested review from @freeCodeCamp/dev-team
# Should be passed only as 'dev-team'
description: 'Requested organization team PR reviewers'
required: false

View File

@ -1,101 +0,0 @@
/* eslint-disable import/no-unresolved */
/* eslint-disable camelcase */
const core = require('@actions/core');
const githubRoot = require('@actions/github');
(async () => {
try {
const token = core.getInput('github-token');
const branch = core.getInput('branch');
const [owner, repo] = core.getInput('owner-repo').split('/');
if (!owner || !repo) {
core.setFailed('Must specify a valid ownerName/repoName');
}
const base = core.getInput('base');
const title = core.getInput('title');
const body = core.getInput('body');
const labelsStr = core.getInput('labels');
const labels = labelsStr.trim().split(/,\s+/);
const reviewersStr = core.getInput('reviewers');
const reviewers = reviewersStr.trim().split(/,\s+/);
const teamStr = core.getInput('team_reviewers');
const team_reviewers = teamStr.trim().split(/,\s+/);
const github = githubRoot.getOctokit(token);
const branchExists = await github.rest.repos
.getBranch({
owner,
repo,
branch
})
.catch(() => {
console.info('Branch does not exist. Likely no changes in download?');
});
if (!branchExists || branchExists.status !== 200) {
return;
}
const pullRequestExists = await github.rest.pulls.list({
owner,
repo,
head: `${owner}:${branch}`
});
if (pullRequestExists.data.length) {
console.info(
'It looks like a pull request already exists for this branch.'
);
return;
}
const PR = await github.rest.pulls
.create({
owner,
repo,
head: branch,
base,
title,
body
})
.catch(err => {
console.info(
'Unpredicted error occurred when trying to create the PR.'
);
console.error(err);
});
if (!PR || PR.status !== 201) {
return;
}
const prNumber = PR.data.number;
console.log(
`https://github.com/freeCodeCamp/freeCodeCamp/pull/${prNumber} created`
);
if (labels && labels.length) {
await github.rest.issues.addLabels({
owner,
repo,
issue_number: prNumber,
labels
});
console.log(`Labels ${labels} added to PR`);
}
if (reviewers && reviewers.length) {
await github.rest.pulls.requestReviewers({
owner,
repo,
pull_number: prNumber,
reviewers
});
console.log(`Requested Reviewers ${reviewers} added to PR`);
}
if (team_reviewers && team_reviewers.length) {
await github.rest.pulls.requestReviewers({
owner,
repo,
pull_number: prNumber,
team_reviewers
});
console.log(`Requested Team Reviewers ${team_reviewers} added to PR`);
}
} catch (error) {
core.setFailed(error.message);
}
})();

View File

@ -1,5 +0,0 @@
name: 'Remove Deleted English Files From Crowdin'
description: 'Deletes files from Crowdin that are no longer in the English curriculum folder'
runs:
using: 'node12'
main: './index.js'

View File

@ -1,55 +0,0 @@
require('dotenv').config({ path: `${__dirname}/../../.env` });
// const core = require('@actions/core');
const util = require('util');
const exec = util.promisify(require('child_process').exec);
const { getFiles, deleteFile } = require('../../utils/files');
const getOutputFromCommand = async command => {
try {
const { stdout } = await exec(command);
return stdout;
} catch (err) {
console.log('Error');
console.log('command');
console.log(command + '\n');
console.log(err.message);
return null;
}
};
const removeDeletedFiles = async projectId => {
console.log('start deleting source files no longer in English curriculum...');
const crowdinFiles = await getFiles(projectId);
if (crowdinFiles && crowdinFiles.length) {
const challengeCommand = 'find curriculum/challenges/english -name \\*.*';
const listOfEnglishFiles = await getOutputFromCommand(challengeCommand);
const dictionaryCommand =
'find curriculum/dictionaries/english -name \\*.*';
const listOfDictFiles = await getOutputFromCommand(dictionaryCommand);
let curriculumFilesArr = listOfEnglishFiles.split('\n');
curriculumFilesArr = curriculumFilesArr.concat(listOfDictFiles.split('\n'));
if (curriculumFilesArr.length) {
const curriculumLookup = curriculumFilesArr.reduce((obj, filename) => {
return { ...obj, [filename]: 1 };
}, {});
for (let { fileId, path: crowdinFilePath } of crowdinFiles) {
if (
!Object.prototype.hasOwnProperty.call(
curriculumLookup,
crowdinFilePath
)
) {
await deleteFile(projectId, fileId, crowdinFilePath);
}
}
}
} else {
console.log(`WARNING! No Crowdin files found for projectId ${projectId}`);
}
console.log('deleting source non-existent source files complete');
};
const projectId = process.env.CROWDIN_PROJECT_ID;
removeDeletedFiles(projectId);

View File

@ -1,12 +0,0 @@
name: 'Unhide Specific String'
description: 'Updates a specific string to be not hidden'
runs:
using: 'node12'
main: './index.js'
inputs:
filename:
description: 'name of file with specific string to hide'
required: true
string-content:
description: 'text content of string to unhide'
required: true

View File

@ -1,33 +0,0 @@
require('dotenv').config({ path: `${__dirname}/../../.env` });
const core = require('@actions/core');
const { getFiles } = require('../../utils/files');
const { getStrings, changeHiddenStatus } = require('../../utils/strings');
const filename = core.getInput('filename');
const stringContent = core.getInput('string-content');
const hideString = async (projectId, fileName, string) => {
const fileResponse = await getFiles(projectId);
const targetFile = fileResponse.find(el => el.path.endsWith(filename));
if (!targetFile) {
core.setFailed(`${fileName} was not found.`);
return;
}
const stringResponse = await getStrings({
projectId,
fileId: targetFile.fileId
});
const targetString = stringResponse.find(el => el.data.text === string);
if (!targetString) {
core.setFailed(`${string} was not found.`);
return;
}
await changeHiddenStatus(projectId, targetString.data.id, false);
console.log('string unhidden!');
};
const projectId = process.env.CROWDIN_PROJECT_ID;
hideString(projectId, filename, stringContent);

View File

@ -1,30 +0,0 @@
/*
This one-off script can be used to delete all existing translations for a specified language on Crowdin.
Specifying a projectId and lanaguageId in the .env file allows the script to accomplish this task.
*/
require('dotenv').config({ path: `${__dirname}/../.env` });
const {
getLanguageTranslations,
deleteLanguageTranslations
} = require('../utils/strings');
const projectId = process.env.CROWDIN_PROJECT_ID;
const languageId = process.env.CROWDIN_LANGUAGE_ID;
(async (projectId, languageId) => {
console.log('starting script...');
const translations = await getLanguageTranslations({
projectId,
languageId
});
if (translations && translations.length) {
for (let translation of translations) {
const { stringId } = translation.data;
await deleteLanguageTranslations(projectId, languageId, stringId);
}
}
console.log('complete');
})(projectId, languageId);

View File

@ -1,54 +0,0 @@
/*
This one-off script can be used to make any string in a Crowdin project to be marked as "Done" in case a workflow
change inadvertently causes strings, which already have translations (and even an approved translation), to be
reverted to "To Do".
Specifying a projectId in the .env file allows the script to find any string with at least one translation,
adds a new temporary translation to it, and then delete the newly added translation. It is the addition of a
new translation that switches the status back to "Done" in the Crowdsourcing view on Crowdin.
*/
require('dotenv').config({ path: `${__dirname}/../.env` });
const getLanguages = require('../utils/get-languages');
const {
addTranslation,
deleteTranslation,
getLanguageTranslations
} = require('../utils/strings');
const markTranslatedStringsAsDone = async projectId => {
console.log('starting script...');
const languageIds = await getLanguages(projectId);
for (let languageId of languageIds) {
const translations = await getLanguageTranslations({
projectId,
languageId
});
if (translations && translations.length) {
console.log(
`${languageId} has ${translations.length} strings with at least one translation`
);
for (let translation of translations) {
const { stringId } = translation.data;
const newTranslation = await addTranslation(
projectId,
stringId,
languageId,
'this is a camperbot test translation'
);
if (newTranslation && newTranslation.id);
console.log(
`added new translation (translationId: ${newTranslation.id}) for stringId: ${stringId}`
);
await deleteTranslation(projectId, newTranslation.id);
}
}
}
console.log('complete');
};
const projectId = process.env.CROWDIN_PROJECT_ID;
markTranslatedStringsAsDone(projectId);

View File

@ -1,35 +0,0 @@
{
"name": "@freecodecamp/crowdin",
"version": "0.0.1",
"description": "The freeCodeCamp.org open-source codebase and curriculum",
"license": "BSD-3-Clause",
"private": true,
"engines": {
"node": ">=16",
"npm": ">=8"
},
"repository": {
"type": "git",
"url": "git+https://github.com/freeCodeCamp/freeCodeCamp.git"
},
"bugs": {
"url": "https://github.com/freeCodeCamp/freeCodeCamp/issues"
},
"homepage": "https://github.com/freeCodeCamp/freeCodeCamp#readme",
"author": "freeCodeCamp <team@freecodecamp.org>",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"dependencies": {
"@actions/core": "1.10.0",
"@actions/github": "5.1.1",
"dotenv": "16.0.3",
"fs-extra": "10.0.0",
"gray-matter": "4.0.3",
"node-fetch": "2.6.9",
"node-opencc": "2.0.1",
"path": "0.12.7",
"readdirp": "3.6.0"
}
}

View File

@ -1,4 +0,0 @@
CROWDIN_PROJECT_ID=
CROWDIN_PERSONAL_TOKEN=
CROWDIN_API_URL='https://freecodecamp.crowdin.com/api/v2/'
CROWDIN_LANGUAGE_ID=

View File

@ -1,7 +0,0 @@
require('dotenv').config();
const authHeader = {
Authorization: `Bearer ${process.env.CROWDIN_PERSONAL_TOKEN}`
};
module.exports = authHeader;

View File

@ -1,5 +0,0 @@
const delay = (time = 2000) => {
return new Promise(resolve => setTimeout(() => resolve(true), time));
};
module.exports = delay;

View File

@ -1,86 +0,0 @@
const authHeader = require('./auth-header');
const delay = require('./delay');
const makeRequest = require('./make-request');
const getDirs = async projectId => {
let headers = { ...authHeader };
let done = false;
let offset = 0;
let files = [];
while (!done) {
const endPoint = `projects/${projectId}/directories?limit=500&offset=${offset}`;
await delay(1000);
const response = await makeRequest({
method: 'get',
endPoint,
headers
});
if (response.data) {
if (response.data.length) {
files = [...files, ...response.data];
offset += 500;
} else {
done = true;
return files;
}
} else {
const { error } = response;
console.log(error.errorcode);
console.log(error.messsage);
}
}
return null;
};
const addDir = async (projectId, dirName, parentDirId) => {
let headers = { ...authHeader };
const endPoint = `projects/${projectId}/directories`;
let body = {
name: dirName
};
if (parentDirId) {
body = { ...body, directoryId: parentDirId };
}
const response = await makeRequest({
method: 'post',
endPoint,
headers,
body
});
return response;
};
const createDirs = async (crowdinDirs, dirPath) => {
// superParent is the top level directory on crowdin
const superParent = crowdinDirs.find(dir => !dir.data.directoryId);
let lastParentId = superParent.data.id;
const splitDirPath = dirPath.split('/');
splitDirPath.shift();
// we are assuming that the first directory in 'newFile' is the same as the superParent
// maybe throw a check in here to verify that's true
const findCurrDir = (directory, crowdinDirs) => {
return crowdinDirs.find(({ data: { name, directoryId } }) => {
return name === directory && directoryId === lastParentId;
});
};
for (let directory of splitDirPath) {
const currentDirectory = findCurrDir(directory, crowdinDirs);
if (!currentDirectory) {
const response = await addDir(10, directory, lastParentId);
lastParentId = response.data.id;
} else {
lastParentId = currentDirectory.data.id;
}
}
return lastParentId;
};
module.exports = {
addDir,
getDirs,
createDirs
};

View File

@ -1,122 +0,0 @@
const authHeader = require('./auth-header');
const makeRequest = require('./make-request');
const addFile = async (projectId, filename, fileContent, directoryId) => {
let headers = { ...authHeader };
headers['Crowdin-API-FileName'] = filename;
const endPoint = `storages`;
const contentType = 'application/text';
const body = fileContent;
const storageResponse = await makeRequest({
method: 'post',
contentType,
endPoint,
headers,
body
});
if (storageResponse.data) {
const fileBody = {
storageId: storageResponse.data.id,
name: filename,
directoryId
};
const fileResponse = await makeRequest({
method: 'post',
endPoint: `projects/${projectId}/files`,
headers,
body: fileBody
});
if (fileResponse.data) {
return fileResponse.data;
} else {
console.log('error');
console.dir(fileResponse, { depth: null, colors: true });
}
}
return null;
};
const updateFile = async (projectId, fileId, fileContent) => {
let headers = { ...authHeader };
const endPoint = `storages`;
const contentType = 'application/text';
const body = fileContent;
const storageResponse = await makeRequest({
method: 'post',
contentType,
endPoint,
headers,
body
});
if (storageResponse.data) {
const fileBody = {
storageId: storageResponse.data.id
};
const fileResponse = await makeRequest({
method: 'put',
endPoint: `projects/${projectId}/files${fileId}`,
headers,
body: fileBody
});
if (fileResponse.data) {
return fileResponse.data;
} else {
console.log('error');
console.dir(fileResponse, { depth: null, colors: true });
}
}
return null;
};
const deleteFile = async (projectId, fileId, filePath) => {
let headers = { ...authHeader };
const endPoint = `projects/${projectId}/files/${fileId}`;
await makeRequest({
method: 'delete',
endPoint,
headers
});
console.log(`Deleted ${filePath} from Crowdin project`);
return null;
};
const getFiles = async projectId => {
let headers = { ...authHeader };
let done = false;
let offset = 0;
let files = [];
while (!done) {
const endPoint = `projects/${projectId}/files?limit=500&offset=${offset}`;
const response = await makeRequest({
method: 'get',
endPoint,
headers
});
if (response.data) {
if (response.data.length) {
files = [...files, ...response.data];
offset += 500;
} else {
done = true;
files = files.map(({ data: { directoryId, id: fileId, path } }) => {
// remove leading forwardslash
path = path.slice(1);
return { directoryId, fileId, path };
});
return files;
}
} else {
const { error } = response;
console.log(error.errorcode);
console.log(error.messsage);
}
}
return null;
};
module.exports = {
addFile,
updateFile,
deleteFile,
getFiles
};

View File

@ -1,17 +0,0 @@
const authHeader = require('./auth-header');
const makeRequest = require('./make-request');
const getLanguages = async projectId => {
let headers = { ...authHeader };
const endPoint = `projects/${projectId}?limit=500`;
const response = await makeRequest({ method: 'get', endPoint, headers });
if (response.data && response.data.targetLanguageIds.length) {
return response.data.targetLanguageIds;
} else {
const { error, errors } = response;
console.error(error ? error : errors);
return null;
}
};
module.exports = getLanguages;

View File

@ -1,34 +0,0 @@
require('dotenv').config();
const fetch = require('node-fetch');
const makeRequest = async ({
method,
endPoint,
contentType = 'application/json',
accept = 'application/json',
headers,
body
}) => {
headers = { ...headers, 'Content-Type': contentType, Accept: accept };
const apiUrl = process.env.CROWDIN_API_URL + endPoint;
if (contentType === 'application/x-www-form-urlencoded') {
body = Object.entries(body)
.reduce((formDataArr, [key, value]) => {
return formDataArr.concat(`${key}=${value}`);
}, [])
.join('&');
} else if (contentType === 'application/json') {
body = JSON.stringify(body);
}
const response = await fetch(apiUrl, { headers, method, body });
if (method !== 'delete') {
const data = await response.json();
return data;
} else {
return null;
}
};
module.exports = makeRequest;

View File

@ -1,239 +0,0 @@
const authHeader = require('./auth-header');
const makeRequest = require('./make-request');
const isReservedHeading = (context, str) => {
const reservedHeadings = [
'after-user-code',
'answers',
'before-user-code',
'description',
'fcc-editable-region',
'hints',
'instructions',
'question',
'assignments',
'seed',
'seed-contents',
'solutions',
'text',
'video-solution'
];
const captureGroupStr = `(${reservedHeadings.join('|')})`;
const regex = new RegExp(`--${captureGroupStr}--`);
return !!(context.match(/^Headline/) && str.match(regex));
};
const isCode = str => /^\/pre\/code|\/code$/.test(str);
const isTitle = str => str.endsWith('title');
const shouldHide = (text, context, challengeTitle, crowdinFilePath) => {
if (crowdinFilePath.endsWith('.yml')) {
return !isTitle(context);
}
if (isReservedHeading(context, text) || isCode(context)) {
return true;
}
return text !== challengeTitle && context.includes('id=front-matter');
};
const getStrings = async ({ projectId, fileId }) => {
let headers = { ...authHeader };
let done = false;
let offset = 0;
let strings = [];
while (!done) {
let endPoint = `projects/${projectId}/strings?limit=500&offset=${offset}`;
if (fileId) {
endPoint += `&fileId=${fileId}`;
}
const response = await makeRequest({ method: 'get', endPoint, headers });
if (response.data) {
if (response.data.length) {
strings = [...strings, ...response.data];
offset += 500;
} else {
done = true;
return strings;
}
} else {
const { error, errors } = response;
console.error(error ? error : errors);
}
}
return null;
};
const updateString = async ({ projectId, stringId, propsToUpdate }) => {
let headers = { ...authHeader };
const endPoint = `projects/${projectId}/strings/${stringId}`;
const body = propsToUpdate.map(({ path, value }) => ({
op: 'replace',
path,
value
}));
await makeRequest({
method: 'patch',
endPoint,
headers,
body
});
};
const changeHiddenStatus = async (projectId, stringId, newStatus) => {
await updateString({
projectId,
stringId,
propsToUpdate: [{ path: '/isHidden', value: newStatus }]
});
};
const updateFileStrings = async ({ projectId, fileId, challengeTitle }) => {
const fileStrings = await getStrings({
projectId,
fileId
});
for (let {
data: { id: stringId, text, isHidden, context }
} of fileStrings) {
const hideString = shouldHide(text, context, challengeTitle);
if (!isHidden && hideString) {
changeHiddenStatus(projectId, stringId, true);
} else if (isHidden && !hideString) {
changeHiddenStatus(projectId, stringId, false);
}
}
};
const updateFileString = async ({
projectId,
string,
challengeTitle,
crowdinFilePath
}) => {
const {
data: { id: stringId, text, isHidden, context }
} = string;
const hideString = shouldHide(text, context, challengeTitle, crowdinFilePath);
if (!isHidden && hideString) {
await changeHiddenStatus(projectId, stringId, true);
console.log(
`${challengeTitle} - stringId: ${stringId} - changed isHidden to true`
);
} else if (isHidden && !hideString) {
await changeHiddenStatus(projectId, stringId, false);
console.log(
`${challengeTitle} - stringId: ${stringId} - changed isHidden to false`
);
}
};
const getStringTranslations = async ({ projectId, stringId, languageId }) => {
let headers = { ...authHeader };
let done = false;
let offset = 0;
let translations = [];
while (!done) {
let endPoint = `projects/${projectId}/translations?stringId=${stringId}&languageId=${languageId}&limit=500&offset=${offset}`;
const response = await makeRequest({ method: 'get', endPoint, headers });
if (response.data) {
if (response.data.length) {
translations = [...translations, ...response.data];
offset += 500;
} else {
done = true;
return translations;
}
} else {
const { error, errors } = response;
console.error(
error ? JSON.stringify(error, null, 2) : JSON.stringify(errors, null, 2)
);
}
}
return null;
};
const deleteTranslation = async (projectId, translationId) => {
let headers = { ...authHeader };
const endPoint = `projects/${projectId}/translations/${translationId}`;
await makeRequest({
method: 'delete',
endPoint,
headers
});
console.log(`Deleted translationId ${translationId} from Crowdin project`);
return null;
};
const addTranslation = async (projectId, stringId, languageId, text) => {
let headers = { ...authHeader };
const endPoint = `projects/${projectId}/translations`;
const body = {
stringId,
languageId,
text
};
const response = await makeRequest({
method: 'post',
endPoint,
headers,
body
});
if (response.data) {
return response.data;
}
return null;
};
const getLanguageTranslations = async ({ projectId, languageId }) => {
let headers = { ...authHeader };
let done = false;
let offset = 0;
let translations = [];
while (!done) {
let endPoint = `projects/${projectId}/languages/${languageId}/translations?limit=500&offset=${offset}`;
const response = await makeRequest({ method: 'get', endPoint, headers });
if (response.data) {
if (response.data.length) {
translations = [...translations, ...response.data];
offset += 500;
} else {
done = true;
return translations;
}
} else {
const { error, errors } = response;
console.error(
error ? JSON.stringify(error, null, 2) : JSON.stringify(errors, null, 2)
);
}
}
return null;
};
const deleteLanguageTranslations = async (projectId, languageId, stringId) => {
let headers = { ...authHeader };
const endPoint = `projects/${projectId}/translations?languageId=${languageId}&stringId=${stringId}`;
console.log(`deleting ${stringId}...`);
await makeRequest({
method: 'delete',
endPoint,
headers
});
return null;
};
module.exports = {
getStrings,
updateString,
updateFileStrings,
updateFileString,
getStringTranslations,
addTranslation,
deleteTranslation,
getLanguageTranslations,
deleteLanguageTranslations,
changeHiddenStatus
};