From bbb6f284e21d753ff1cb39aa49be7675838e7ee5 Mon Sep 17 00:00:00 2001 From: Dhruv Bhanushali Date: Fri, 17 Feb 2023 21:21:23 +0400 Subject: [PATCH] Download translations in bulk to prevent GlotPress throttling (#2188) Co-authored-by: Zack Krida --- .github/workflows/ghcr.yml | 8 + .gitignore | 11 +- Dockerfile | 1 - package.json | 2 + pnpm-lock.yaml | 35 +- src/locales/scripts/bulk-download.js | 116 ++ src/locales/scripts/get-translations.js | 130 +- src/locales/scripts/locales-list.json | 2391 ---------------------- src/locales/scripts/separate-download.js | 57 + src/locales/scripts/utils.js | 40 + 10 files changed, 266 insertions(+), 2525 deletions(-) create mode 100644 src/locales/scripts/bulk-download.js delete mode 100644 src/locales/scripts/locales-list.json create mode 100644 src/locales/scripts/separate-download.js diff --git a/.github/workflows/ghcr.yml b/.github/workflows/ghcr.yml index 71752b99e2..a72cb417d0 100644 --- a/.github/workflows/ghcr.yml +++ b/.github/workflows/ghcr.yml @@ -38,6 +38,14 @@ jobs: with: ref: ${{ github.event.release.tag_name }} + - uses: ./.github/actions/setup-node-env + + - name: Download translation strings + run: pnpm i18n + env: + GLOTPRESS_USERNAME: ${{ secrets.MAKE_USERNAME }} + GLOTPRESS_PASSWORD: ${{ secrets.MAKE_LOGIN_PASSWORD }} + - uses: docker/setup-buildx-action@v2 with: install: true diff --git a/.gitignore b/.gitignore index 319443fc66..6f2840e923 100644 --- a/.gitignore +++ b/.gitignore @@ -22,13 +22,9 @@ selenium-debug.log vercel.json .eslintcache .nuxt -src/locales/*.json .nuxt-storybook storybook-static .vercel -/src/locales/scripts/valid-locales.json -/src/locales/scripts/untranslated-locales.json -/src/locales/scripts/wp-locales.json .zshrc .tcv-export @@ -44,3 +40,10 @@ test/Default # Ignore generated translation files *.pot + +# Ignore downloaded translation files +/src/locales/openverse.zip +/src/locales/*.json +/src/locales/scripts/valid-locales.json +/src/locales/scripts/untranslated-locales.json +/src/locales/scripts/wp-locales.json diff --git a/Dockerfile b/Dockerfile index d8b07cf334..182163313c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -30,7 +30,6 @@ ARG RELEASE RUN echo "{\"release\":\"${RELEASE}\"}" > /home/node/app/src/static/version.json -RUN pnpm i18n RUN pnpm build:only ################### diff --git a/package.json b/package.json index 172a372cba..5c0435293c 100644 --- a/package.json +++ b/package.json @@ -144,6 +144,7 @@ "@typescript-eslint/parser": "^5.44.0", "@vue/runtime-dom": "^3.2.37", "@vue/test-utils": "^1.1.3", + "adm-zip": "^0.5.10", "autoprefixer": "^10.4.0", "axios-rate-limit": "^1.3.0", "babel-jest": "^26.6.3", @@ -170,6 +171,7 @@ "postcss": "^8.4.12", "prettier": "^2.8.3", "prettier-plugin-tailwindcss": "^0.2.2", + "qs": "^6.11.0", "rimraf": "^3.0.2", "tailwind-config-viewer": "^1.6.3", "tailwindcss": "^3.2.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cb0490a530..7711529ed3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -46,6 +46,7 @@ specifiers: '@vue/test-utils': ^1.1.3 '@vueuse/core': ^9.3.0 '@wordpress/is-shallow-equal': ^4.6.0 + adm-zip: ^0.5.10 async-mutex: ^0.3.2 autoprefixer: ^10.4.0 axios: ^0.27.0 @@ -92,6 +93,7 @@ specifiers: prettier: ^2.8.3 prettier-plugin-tailwindcss: ^0.2.2 prom-client: ^14.0.1 + qs: ^6.11.0 rfdc: ^1.3.0 rimraf: ^3.0.2 seeded-rand: ^2.0.1 @@ -189,6 +191,7 @@ devDependencies: '@typescript-eslint/parser': 5.44.0_hsf322ms6xhhd4b5ne6lb74y4a '@vue/runtime-dom': 3.2.38 '@vue/test-utils': 1.1.3_42puyn3dcxirnpdjnosl7pbb6a + adm-zip: 0.5.10 autoprefixer: 10.4.0_postcss@8.4.12 axios-rate-limit: 1.3.0_axios@0.27.2 babel-jest: 26.6.3_@babel+core@7.20.12 @@ -215,6 +218,7 @@ devDependencies: postcss: 8.4.12 prettier: 2.8.3 prettier-plugin-tailwindcss: 0.2.2_prettier@2.8.3 + qs: 6.11.0 rimraf: 3.0.2 tailwind-config-viewer: 1.6.3_tailwindcss@3.2.4 tailwindcss: 3.2.4_ts-node@10.9.1 @@ -3783,7 +3787,7 @@ packages: '@storybook/core-events': 6.5.10 core-js: 3.27.2 global: 4.4.0 - qs: 6.10.3 + qs: 6.11.0 telejson: 6.0.8 dev: true @@ -3830,7 +3834,7 @@ packages: global: 4.4.0 lodash: 4.17.21 memoizerific: 1.11.3 - qs: 6.10.3 + qs: 6.11.0 react: 16.14.0 react-dom: 16.14.0_react@16.14.0 regenerator-runtime: 0.13.11 @@ -3863,7 +3867,7 @@ packages: '@storybook/theming': 6.5.10 core-js: 3.27.2 memoizerific: 1.11.3 - qs: 6.10.3 + qs: 6.11.0 regenerator-runtime: 0.13.11 util-deprecate: 1.0.2 dev: true @@ -3884,7 +3888,7 @@ packages: '@storybook/theming': 6.5.10_wcqkhtmu7mswc6yz4uyexck3ty core-js: 3.27.2 memoizerific: 1.11.3 - qs: 6.10.3 + qs: 6.11.0 react: 16.14.0 react-dom: 16.14.0_react@16.14.0 regenerator-runtime: 0.13.11 @@ -3921,7 +3925,7 @@ packages: core-js: 3.27.2 global: 4.4.0 lodash: 4.17.21 - qs: 6.10.3 + qs: 6.11.0 react: 16.14.0 react-dom: 16.14.0_react@16.14.0 regenerator-runtime: 0.13.11 @@ -4399,7 +4403,7 @@ packages: core-js: 3.27.2 global: 4.4.0 lodash: 4.17.21 - qs: 6.10.3 + qs: 6.11.0 regenerator-runtime: 0.13.11 synchronous-promise: 2.0.15 ts-dedent: 2.2.0 @@ -4428,7 +4432,7 @@ packages: core-js: 3.27.2 global: 4.4.0 lodash: 4.17.21 - qs: 6.10.3 + qs: 6.11.0 react: 16.14.0 react-dom: 16.14.0_react@16.14.0 regenerator-runtime: 0.13.11 @@ -4471,7 +4475,7 @@ packages: '@storybook/client-logger': 6.5.10 core-js: 3.27.2 memoizerific: 1.11.3 - qs: 6.10.3 + qs: 6.11.0 regenerator-runtime: 0.13.11 dev: true @@ -4489,7 +4493,7 @@ packages: '@storybook/client-logger': 6.5.10 core-js: 3.27.2 memoizerific: 1.11.3 - qs: 6.10.3 + qs: 6.11.0 react: 16.14.0 react-dom: 16.14.0_react@16.14.0 regenerator-runtime: 0.13.11 @@ -4670,7 +4674,7 @@ packages: '@storybook/theming': 6.5.10_wcqkhtmu7mswc6yz4uyexck3ty core-js: 3.27.2 memoizerific: 1.11.3 - qs: 6.10.3 + qs: 6.11.0 react: 16.14.0 react-dom: 16.14.0_react@16.14.0 regenerator-runtime: 0.13.11 @@ -6044,6 +6048,11 @@ packages: engines: {node: '>= 0.12.0'} dev: true + /adm-zip/0.5.10: + resolution: {integrity: sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==} + engines: {node: '>=6.0'} + dev: true + /agent-base/6.0.2: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} @@ -13970,7 +13979,7 @@ packages: dependencies: is-ssh: 1.3.3 protocols: 1.4.8 - qs: 6.10.3 + qs: 6.11.0 query-string: 6.14.1 dev: false @@ -15509,8 +15518,8 @@ packages: engines: {node: '>=0.6.0', teleport: '>=0.2.0'} dev: false - /qs/6.10.3: - resolution: {integrity: sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==} + /qs/6.11.0: + resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} engines: {node: '>=0.6'} dependencies: side-channel: 1.0.4 diff --git a/src/locales/scripts/bulk-download.js b/src/locales/scripts/bulk-download.js new file mode 100644 index 0000000000..1cc5dfcb8f --- /dev/null +++ b/src/locales/scripts/bulk-download.js @@ -0,0 +1,116 @@ +const { pipeline } = require("stream/promises") + +const { createWriteStream } = require("fs") + +const qs = require("qs") +const AdmZip = require("adm-zip") + +const { writeLocaleFile } = require("./utils") +const axios = require("./axios") +const jed1xJsonToJson = require("./jed1x-json-to-json") + +const LOGIN_URL = "https://login.wordpress.org/wp-login.php" +const BULK_DOWNLOAD_URL = + "https://translate.wordpress.org/exporter/meta/openverse/-do/" + +/** + * Given a username and password, login to WordPress and get the authentication + * cookies from the `Set-Cookie` header. + * + * @param log {string} - the username to log in with + * @param pwd {string} - the password for the given username + * @return {Promise} - the list of cookies in the `Set-Cookie` header + */ +const getAuthCookies = async (log, pwd) => { + const res = await axios.post( + LOGIN_URL, + qs.stringify({ + log, + pwd, + rememberme: "forever", + "wp-submit": "Log In", + redirect_to: "https://make.wordpress.org/", + }), + { + headers: { "content-type": "application/x-www-form-urlencoded" }, + maxRedirects: 0, + validateStatus: () => true, + } + ) + if ( + res.status == 302 && + res.headers["set-cookie"].join(" ").includes("wporg_logged_in") + ) { + return res.headers["set-cookie"].map((cookie) => + cookie.substring(0, cookie.indexOf(";")) + ) + } + throw new Error(`Authentication failed: server returned ${res.status}`) +} + +/** + * Fetch the ZIP of translations strings from GlotPress using the authentication + * cookies to access the page. + * + * @param cookies {string[]} - the cookies to authenticate the ZIP download + * @return {Promise}} - the path to the downloaded ZIP file + */ +const fetchBulkJed1x = async (cookies) => { + const res = await axios.get(BULK_DOWNLOAD_URL, { + headers: { cookie: cookies.join(";") }, + params: { "export-format": "jed1x" }, + responseType: "stream", + }) + const destPath = process.cwd() + "/src/locales/openverse.zip" + await pipeline(res.data, createWriteStream(destPath)) + return destPath +} + +/** + * Extract all JSON file from the given ZIP file. Their names are sanitised to + * be in the format `.json`. + * + * @param zipPath {string} - the path to the ZIP file to extract + * @return {Promise} - the outcome of writing all ZIP files + */ +const extractZip = async (zipPath) => { + const zip = new AdmZip(zipPath, undefined) + const localeJsonMap = zip + .getEntries() + .filter((entry) => entry.entryName.endsWith(".json")) + .map((entry) => { + const jed1xObj = JSON.parse(zip.readAsText(entry)) + const vueI18nObj = jed1xJsonToJson(jed1xObj) + const localeName = entry.name + .replace("meta-openverse-", "") + .replace(".jed.json", "") + return [localeName, vueI18nObj] + }) + return await Promise.all( + localeJsonMap.map((args) => writeLocaleFile(...args)) + ) +} + +/** + * Perform a bulk download of translation strings from GlotPress and extrat the + * JSON files from the ZIP archive. + * + * @return {Promise} - whether the bulk download succeeded + */ +const bulkDownload = async () => { + console.log("Performing bulk download.") + const username = process.env.GLOTPRESS_USERNAME + const password = process.env.GLOTPRESS_PASSWORD + + if (!(username && password)) { + console.log("Auth credentials not found, bulk download cancelled.") + throw new Error("Bulk download cancelled") + } + + const cookies = await getAuthCookies(username, password) + const zipPath = await fetchBulkJed1x(cookies) + const translations = await extractZip(zipPath) + console.log(`Successfully saved ${translations.length} translations.`) +} + +module.exports = bulkDownload diff --git a/src/locales/scripts/get-translations.js b/src/locales/scripts/get-translations.js index 61925ce5f2..67c64fd95f 100644 --- a/src/locales/scripts/get-translations.js +++ b/src/locales/scripts/get-translations.js @@ -2,123 +2,16 @@ * Fetch the NGX-Translate JSON file for each supported language, * convert to our JSON format, and save in the correct folder. */ -const { writeFile } = require("fs/promises") + const { writeFileSync } = require("fs") const os = require("os") const chokidar = require("chokidar") -const axios = require("./axios") - -const jed1xJsonToJson = require("./jed1x-json-to-json") const { parseJson } = require("./read-i18n") -/** - * - * @typedef {"json"|"jed1x"|"ngx"} JSONFormat - * @returns - */ - -/** - * A GlotPress Output format for translation strings - * @typedef {("android"|"po"|"mo"|"resx"|"strings"|"properties"|"json"|"jed1x"|"ngx" & JSONFormat)} Format - */ - -const baseUrl = `https://translate.wordpress.org/projects/meta/openverse` - -/** - * - * @param {Format} format - * @returns {(localeCode: string) => string} - */ -const makeTranslationUrl = - (format = "po") => - (localeCode = "en-gb") => - `${baseUrl}/${localeCode}/default/export-translations/?format=${format}` - -/** - * fetch a json translation from GlotPress - * @param {string} locale - */ -const fetchJed1xTranslation = (locale) => - axios - .get(makeTranslationUrl("jed1x")(locale)) - .then((res) => res.data) - .catch((err) => err.response.status) - -const replacePlaceholders = (json) => { - if (json === null) { - return null - } - if (typeof json === "string") { - return json.replace(/###([a-zA-Z-]*)###/g, "{$1}") - } - let currentJson = { ...json } - - for (const row of Object.entries(currentJson)) { - let [key, value] = row - currentJson[key] = replacePlaceholders(value) - } - return currentJson -} -/** - * Write translation strings to a file in the locale directory - * @param {string} locale - * @param {any} rawTranslations - */ -const writeLocaleFile = (locale, rawTranslations) => { - const translations = replacePlaceholders(rawTranslations) - return writeFile( - process.cwd() + `/src/locales/${locale}.json`, - JSON.stringify(translations, null, 2) + os.EOL - ) -} - -/** - * Write a file for each translation object - * @param {{[locale: string]: {[translation: string]: string}}} translationsByLocale - */ -const writeLocaleFiles = (translationsByLocale) => - Promise.all( - Object.entries(translationsByLocale).map(([locale, translations]) => - writeLocaleFile(locale, translations) - ) - ) - -// Check if an object is empty -const isEmpty = (obj) => Object.values(obj).every((x) => x === null) - -/** - * Write translation files to the "src/locales" directory from - * the supplied list of locales - * - * @param {string[]} locales - */ -const fetchAndConvertJed1xTranslations = (locales) => { - return Promise.allSettled(locales.map(fetchJed1xTranslation)) - .then((res) => { - let successfulTranslations = [] - let failedTranslations = [] - res.forEach(({ status, value }, index) => { - if (status === "fulfilled" && !isEmpty(value)) { - successfulTranslations[locales[index]] = value - } else { - failedTranslations.push(`${locales[index]} (${value})`) - } - }) - if (failedTranslations.length) { - console.log(`Failed to fetch ${failedTranslations.join(", ")}`) - } - return successfulTranslations - }) - .then((res) => { - Object.keys(res).forEach((key) => { - res[key] = jed1xJsonToJson(res[key]) - }) - return res - }) - .then(writeLocaleFiles) -} +const bulkDownload = require("./bulk-download") +const separateDownload = require("./separate-download") /** * Write `en.json` from `en.json5`. @@ -144,11 +37,16 @@ if (process.argv.includes("--watch")) { } if (!process.argv.includes("--en-only")) { - const localeJSON = require("./wp-locales.json") - - fetchAndConvertJed1xTranslations(Object.values(localeJSON).map((i) => i.slug)) - .then((res) => { - console.log(`Successfully saved ${res.length + 1} translations.`) + bulkDownload() + .catch((err) => { + console.error(err) + return separateDownload() + }) + .catch((err) => { + console.error(err) + console.error(":'-( Downloading translations failed.") + if (process.argv.includes("--require-complete")) { + process.exitCode = 1 + } }) - .catch(console.error) } diff --git a/src/locales/scripts/locales-list.json b/src/locales/scripts/locales-list.json deleted file mode 100644 index 2b1689bdf9..0000000000 --- a/src/locales/scripts/locales-list.json +++ /dev/null @@ -1,2391 +0,0 @@ -{ - "es_ES": { - "name": "Spanish (Spain)", - "nativeName": "Español", - "langCodeIso_639_1": "es", - "langCodeIso_639_2": "spa", - "countryCode": "es", - "slug": "es", - "googleCode": "es", - "facebookLocale": "es_ES", - "wpLocale": "es_ES", - "code": "es", - "translated": 100 - }, - "es_EC": { - "name": "Spanish (Ecuador)", - "nativeName": "Español de Ecuador", - "langCodeIso_639_1": "es", - "langCodeIso_639_2": "spa", - "countryCode": "ec", - "slug": "es-ec", - "googleCode": "es", - "facebookLocale": "es_LA", - "wpLocale": "es_EC", - "code": "es-ec", - "translated": 100 - }, - "es_VE": { - "name": "Spanish (Venezuela)", - "nativeName": "Español de Venezuela", - "langCodeIso_639_1": "es", - "langCodeIso_639_2": "spa", - "countryCode": "ve", - "slug": "es-ve", - "googleCode": "es", - "facebookLocale": "es_LA", - "wpLocale": "es_VE", - "code": "es-ve", - "translated": 100 - }, - "en_GB": { - "name": "English (UK)", - "nativeName": "English (UK)", - "langCodeIso_639_1": "en", - "langCodeIso_639_2": "eng", - "countryCode": "gb", - "slug": "en-gb", - "googleCode": "en", - "facebookLocale": "en_GB", - "wpLocale": "en_GB", - "code": "en-gb", - "translated": 100 - }, - "es_CO": { - "name": "Spanish (Colombia)", - "nativeName": "Español de Colombia", - "langCodeIso_639_1": "es", - "langCodeIso_639_2": "spa", - "countryCode": "co", - "slug": "es-co", - "googleCode": "es", - "facebookLocale": "es_LA", - "wpLocale": "es_CO", - "code": "es-co", - "translated": 100 - }, - "ro_RO": { - "name": "Romanian", - "nativeName": "Română", - "langCodeIso_639_1": "ro", - "langCodeIso_639_2": "ron", - "countryCode": "ro", - "slug": "ro", - "pluralExpression": "(n == 1) ? 0 : ((n == 0 || n % 100 >= 2 && n % 100 <= 19) ? 1 : 2)", - "googleCode": "ro", - "facebookLocale": "ro_RO", - "wpLocale": "ro_RO", - "code": "ro", - "translated": 100 - }, - "nl_NL": { - "name": "Dutch", - "nativeName": "Nederlands", - "langCodeIso_639_1": "nl", - "langCodeIso_639_2": "nld", - "countryCode": "nl", - "slug": "nl", - "googleCode": "nl", - "facebookLocale": "nl_NL", - "wpLocale": "nl_NL", - "code": "nl", - "translated": 99 - }, - "cs_CZ": { - "name": "Czech", - "nativeName": "Čeština", - "langCodeIso_639_1": "cs", - "langCodeIso_639_2": "ces", - "countryCode": "cz", - "slug": "cs", - "pluralExpression": "(n == 1) ? 0 : ((n >= 2 && n <= 4) ? 1 : 2)", - "googleCode": "cs", - "facebookLocale": "cs_CZ", - "wpLocale": "cs_CZ", - "code": "cs", - "translated": 99 - }, - "de_DE": { - "name": "German", - "nativeName": "Deutsch", - "langCodeIso_639_1": "de", - "countryCode": "de", - "slug": "de", - "googleCode": "de", - "facebookLocale": "de_DE", - "wpLocale": "de_DE", - "code": "de", - "translated": 99 - }, - "sq": { - "name": "Albanian", - "nativeName": "Shqip", - "langCodeIso_639_1": "sq", - "langCodeIso_639_2": "sqi", - "countryCode": "al", - "slug": "sq", - "googleCode": "sq", - "facebookLocale": "sq_AL", - "wpLocale": "sq", - "code": "sq", - "translated": 99 - }, - "gl_ES": { - "name": "Galician", - "nativeName": "Galego", - "langCodeIso_639_1": "gl", - "langCodeIso_639_2": "glg", - "countryCode": "es", - "slug": "gl", - "googleCode": "gl", - "facebookLocale": "gl_ES", - "wpLocale": "gl_ES", - "code": "gl", - "translated": 98 - }, - "da_DK": { - "name": "Danish", - "nativeName": "Dansk", - "langCodeIso_639_1": "da", - "langCodeIso_639_2": "dan", - "countryCode": "dk", - "slug": "da", - "googleCode": "da", - "facebookLocale": "da_DK", - "wpLocale": "da_DK", - "code": "da", - "translated": 98 - }, - "es_AR": { - "name": "Spanish (Argentina)", - "nativeName": "Español de Argentina", - "langCodeIso_639_1": "es", - "langCodeIso_639_2": "spa", - "countryCode": "ar", - "slug": "es-ar", - "googleCode": "es", - "facebookLocale": "es_LA", - "wpLocale": "es_AR", - "code": "es-ar", - "translated": 95 - }, - "en_CA": { - "name": "English (Canada)", - "nativeName": "English (Canada)", - "langCodeIso_639_1": "en", - "langCodeIso_639_2": "eng", - "countryCode": "ca", - "slug": "en-ca", - "googleCode": "en", - "wpLocale": "en_CA", - "code": "en-ca", - "translated": 95 - }, - "en_ZA": { - "name": "English (South Africa)", - "nativeName": "English (South Africa)", - "langCodeIso_639_1": "en", - "langCodeIso_639_2": "eng", - "countryCode": "za", - "slug": "en-za", - "googleCode": "en", - "wpLocale": "en_ZA", - "code": "en-za", - "translated": 95 - }, - "es_DO": { - "name": "Spanish (Dominican Republic)", - "nativeName": "Español de República Dominicana", - "langCodeIso_639_1": "es", - "langCodeIso_639_2": "spa", - "countryCode": "do", - "slug": "es-do", - "googleCode": "es", - "facebookLocale": "es_LA", - "wpLocale": "es_DO", - "code": "es-do", - "translated": 95 - }, - "fr_FR": { - "name": "French (France)", - "nativeName": "Français", - "langCodeIso_639_1": "fr", - "countryCode": "fr", - "slug": "fr", - "pluralExpression": "n > 1", - "googleCode": "fr", - "facebookLocale": "fr_FR", - "wpLocale": "fr_FR", - "code": "fr", - "translated": 90 - }, - "sv_SE": { - "name": "Swedish", - "nativeName": "Svenska", - "langCodeIso_639_1": "sv", - "langCodeIso_639_2": "swe", - "countryCode": "se", - "slug": "sv", - "googleCode": "sv", - "facebookLocale": "sv_SE", - "wpLocale": "sv_SE", - "code": "sv", - "translated": 71 - }, - "zh_CN": { - "name": "Chinese (China)", - "nativeName": "简体中文", - "langCodeIso_639_1": "zh", - "langCodeIso_639_2": "zho", - "countryCode": "cn", - "slug": "zh-cn", - "pluralExpression": "0", - "googleCode": "zh-CN", - "facebookLocale": "zh_CN", - "wpLocale": "zh_CN", - "code": "zh-cn", - "translated": 61 - }, - "id_ID": { - "name": "Indonesian", - "nativeName": "Bahasa Indonesia", - "langCodeIso_639_1": "id", - "langCodeIso_639_2": "ind", - "countryCode": "id", - "slug": "id", - "pluralExpression": "n > 1", - "googleCode": "id", - "facebookLocale": "id_ID", - "wpLocale": "id_ID", - "code": "id", - "translated": 57 - }, - "pt_BR": { - "name": "Portuguese (Brazil)", - "nativeName": "Português do Brasil", - "langCodeIso_639_1": "pt", - "langCodeIso_639_2": "por", - "countryCode": "br", - "slug": "pt-br", - "pluralExpression": "n > 1", - "googleCode": "pt-BR", - "facebookLocale": "pt_BR", - "wpLocale": "pt_BR", - "code": "pt-br", - "translated": 54 - }, - "ru_RU": { - "name": "Russian", - "nativeName": "Русский", - "langCodeIso_639_1": "ru", - "langCodeIso_639_2": "rus", - "countryCode": "ru", - "slug": "ru", - "pluralExpression": "(n % 10 == 1 && n % 100 != 11) ? 0 : ((n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14)) ? 1 : 2)", - "googleCode": "ru", - "facebookLocale": "ru_RU", - "wpLocale": "ru_RU", - "code": "ru", - "translated": 52 - }, - "ja": { - "name": "Japanese", - "nativeName": "日本語", - "langCodeIso_639_1": "ja", - "countryCode": "jp", - "slug": "ja", - "pluralExpression": "0", - "googleCode": "ja", - "facebookLocale": "ja_JP", - "wpLocale": "ja", - "code": "ja", - "translated": 32 - }, - "zh_TW": { - "name": "Chinese (Taiwan)", - "nativeName": "繁體中文", - "langCodeIso_639_1": "zh", - "langCodeIso_639_2": "zho", - "countryCode": "tw", - "slug": "zh-tw", - "pluralExpression": "0", - "googleCode": "zh-TW", - "facebookLocale": "zh_TW", - "wpLocale": "zh_TW", - "code": "zh-tw", - "translated": 30 - }, - "es_MX": { - "name": "Spanish (Mexico)", - "nativeName": "Español de México", - "langCodeIso_639_1": "es", - "langCodeIso_639_2": "spa", - "countryCode": "mx", - "slug": "es-mx", - "googleCode": "es", - "facebookLocale": "es_MX", - "wpLocale": "es_MX", - "code": "es-mx", - "translated": 20 - }, - "nl_BE": { - "name": "Dutch (Belgium)", - "nativeName": "Nederlands (België)", - "langCodeIso_639_1": "nl", - "langCodeIso_639_2": "nld", - "countryCode": "be", - "slug": "nl-be", - "googleCode": "nl", - "wpLocale": "nl_BE", - "code": "nl-be", - "translated": 15 - }, - "kn": { - "name": "Kannada", - "nativeName": "ಕನ್ನಡ", - "langCodeIso_639_1": "kn", - "langCodeIso_639_2": "kan", - "countryCode": "in", - "slug": "kn", - "googleCode": "kn", - "facebookLocale": "kn_IN", - "wpLocale": "kn", - "code": "kn", - "translated": 9 - }, - "it_IT": { - "name": "Italian", - "nativeName": "Italiano", - "langCodeIso_639_1": "it", - "langCodeIso_639_2": "ita", - "countryCode": "it", - "slug": "it", - "googleCode": "it", - "facebookLocale": "it_IT", - "wpLocale": "it_IT", - "code": "it", - "translated": 6 - }, - "ar": { - "name": "Arabic", - "nativeName": "العربية", - "langCodeIso_639_1": "ar", - "langCodeIso_639_2": "ara", - "slug": "ar", - "pluralExpression": "(n == 0) ? 0 : ((n == 1) ? 1 : ((n == 2) ? 2 : ((n % 100 >= 3 && n % 100 <= 10) ? 3 : ((n % 100 >= 11 && n % 100 <= 99) ? 4 : 5))))", - "googleCode": "ar", - "facebookLocale": "ar_AR", - "textDirection": "rtl", - "wpLocale": "ar", - "code": "ar", - "translated": 2 - }, - "en_AU": { - "name": "English (Australia)", - "nativeName": "English (Australia)", - "langCodeIso_639_1": "en", - "langCodeIso_639_2": "eng", - "countryCode": "au", - "slug": "en-au", - "googleCode": "en", - "wpLocale": "en_AU", - "code": "en-au", - "translated": 2 - }, - "pt_PT": { - "name": "Portuguese (Portugal)", - "nativeName": "Português", - "langCodeIso_639_1": "pt", - "countryCode": "pt", - "slug": "pt", - "googleCode": "pt-PT", - "facebookLocale": "pt_PT", - "wpLocale": "pt_PT", - "code": "pt", - "translated": 1 - }, - "skr": { - "name": "Saraiki", - "nativeName": "سرائیکی", - "countryCode": "pk", - "slug": "skr", - "pluralExpression": "n > 1", - "textDirection": "rtl", - "wpLocale": "skr", - "code": "skr", - "translated": 0 - }, - "srd": { - "name": "Sardinian", - "nativeName": "Sardu", - "langCodeIso_639_1": "sc", - "langCodeIso_639_2": "srd", - "countryCode": "it", - "slug": "srd", - "facebookLocale": "sc_IT", - "wpLocale": "srd", - "code": "srd", - "translated": 0 - }, - "gd": { - "name": "Scottish Gaelic", - "nativeName": "Gàidhlig", - "langCodeIso_639_1": "gd", - "langCodeIso_639_2": "gla", - "countryCode": "gb", - "slug": "gd", - "pluralExpression": "(n == 1 || n == 11) ? 0 : ((n == 2 || n == 12) ? 1 : ((n >= 3 && n <= 10 || n >= 13 && n <= 19) ? 2 : 3))", - "googleCode": "gd", - "wpLocale": "gd", - "code": "gd", - "translated": 0 - }, - "nb_NO": { - "name": "Norwegian (Bokmål)", - "nativeName": "Norsk bokmål", - "langCodeIso_639_1": "nb", - "langCodeIso_639_2": "nob", - "countryCode": "no", - "slug": "nb", - "googleCode": "no", - "facebookLocale": "nb_NO", - "wpLocale": "nb_NO", - "code": "nb", - "translated": 0 - }, - "scn": { - "name": "Sicilian", - "nativeName": "Sicilianu", - "countryCode": "it", - "slug": "scn", - "wpLocale": "scn", - "code": "scn", - "translated": 0 - }, - "pcm": { - "name": "Nigerian Pidgin", - "nativeName": "Nigerian Pidgin", - "countryCode": "ng", - "slug": "pcm", - "wpLocale": "pcm", - "code": "pcm", - "translated": 0 - }, - "sr_RS": { - "name": "Serbian", - "nativeName": "Српски језик", - "langCodeIso_639_1": "sr", - "langCodeIso_639_2": "srp", - "countryCode": "rs", - "slug": "sr", - "pluralExpression": "(n % 10 == 1 && n % 100 != 11) ? 0 : ((n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14)) ? 1 : 2)", - "googleCode": "sr", - "facebookLocale": "sr_RS", - "wpLocale": "sr_RS", - "code": "sr", - "translated": 0 - }, - "ne_NP": { - "name": "Nepali", - "nativeName": "नेपाली", - "langCodeIso_639_1": "ne", - "langCodeIso_639_2": "nep", - "countryCode": "np", - "slug": "ne", - "googleCode": "ne", - "facebookLocale": "ne_NP", - "wpLocale": "ne_NP", - "code": "ne", - "translated": 0 - }, - "nn_NO": { - "name": "Norwegian (Nynorsk)", - "nativeName": "Norsk nynorsk", - "langCodeIso_639_1": "nn", - "langCodeIso_639_2": "nno", - "countryCode": "no", - "slug": "nn", - "googleCode": "no", - "facebookLocale": "nn_NO", - "wpLocale": "nn_NO", - "code": "nn", - "translated": 0 - }, - "szl": { - "name": "Silesian", - "nativeName": "Ślōnskŏ gŏdka", - "countryCode": "pl", - "slug": "szl", - "pluralExpression": "(n==1 ? 0 : n%10>=2 && n%10<=4 && n%100==20 ? 1 : 2)", - "facebookLocale": "sz_PL", - "wpLocale": "szl", - "code": "szl", - "translated": 0 - }, - "sq_XK": { - "name": "Shqip (Kosovo)", - "nativeName": "Për Kosovën Shqip", - "langCodeIso_639_1": "sq", - "countryCode": "xk", - "slug": "sq-xk", - "wpLocale": "sq_XK", - "code": "sq-xk", - "translated": 0 - }, - "nqo": { - "name": "N’ko", - "nativeName": "ߒߞߏ", - "langCodeIso_639_2": "nqo", - "countryCode": "gn", - "slug": "nqo", - "textDirection": "rtl", - "wpLocale": "nqo", - "code": "nqo", - "translated": 0 - }, - "my_MM": { - "name": "Myanmar (Burmese)", - "nativeName": "ဗမာစာ", - "langCodeIso_639_1": "my", - "langCodeIso_639_2": "mya", - "countryCode": "mm", - "slug": "mya", - "googleCode": "my", - "wpLocale": "my_MM", - "code": "mya", - "translated": 0 - }, - "sna": { - "name": "Shona", - "nativeName": "ChiShona", - "langCodeIso_639_1": "sn", - "countryCode": "zw", - "slug": "sna", - "wpLocale": "sna", - "code": "sna", - "translated": 0 - }, - "pcd": { - "name": "Picard", - "nativeName": "Ch’ti", - "countryCode": "fr", - "slug": "pcd", - "pluralExpression": "n > 1", - "wpLocale": "pcd", - "code": "pcd", - "translated": 0 - }, - "oci": { - "name": "Occitan", - "nativeName": "Occitan", - "langCodeIso_639_1": "oc", - "langCodeIso_639_2": "oci", - "countryCode": "fr", - "slug": "oci", - "pluralExpression": "n > 1", - "wpLocale": "oci", - "code": "oci", - "translated": 0 - }, - "si_LK": { - "name": "Sinhala", - "nativeName": "සිංහල", - "langCodeIso_639_1": "si", - "langCodeIso_639_2": "sin", - "countryCode": "lk", - "slug": "si", - "googleCode": "si", - "facebookLocale": "si_LK", - "wpLocale": "si_LK", - "code": "si", - "translated": 0 - }, - "pl_PL": { - "name": "Polish", - "nativeName": "Polski", - "langCodeIso_639_1": "pl", - "langCodeIso_639_2": "pol", - "countryCode": "pl", - "slug": "pl", - "pluralExpression": "(n == 1) ? 0 : ((n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14)) ? 1 : 2)", - "googleCode": "pl", - "facebookLocale": "pl_PL", - "wpLocale": "pl_PL", - "code": "pl", - "translated": 0 - }, - "pt_AO": { - "name": "Portuguese (Angola)", - "nativeName": "Português de Angola", - "langCodeIso_639_1": "pt", - "countryCode": "ao", - "slug": "pt-ao", - "wpLocale": "pt_AO", - "code": "pt-ao", - "translated": 0 - }, - "rhg": { - "name": "Rohingya", - "nativeName": "Ruáinga", - "countryCode": "mm", - "slug": "rhg", - "pluralExpression": "0", - "wpLocale": "rhg", - "code": "rhg", - "translated": 0 - }, - "fa_AF": { - "name": "Persian (Afghanistan)", - "nativeName": "(فارسی (افغانستان", - "langCodeIso_639_1": "fa", - "langCodeIso_639_2": "fas", - "slug": "fa-af", - "pluralExpression": "n > 1", - "googleCode": "fa", - "textDirection": "rtl", - "wpLocale": "fa_AF", - "code": "fa-af", - "translated": 0 - }, - "roh": { - "name": "Romansh", - "nativeName": "Rumantsch", - "langCodeIso_639_1": "rm", - "langCodeIso_639_2": "roh", - "countryCode": "ch", - "slug": "roh", - "wpLocale": "roh", - "code": "roh", - "translated": 0 - }, - "sah": { - "name": "Sakha", - "nativeName": "Сахалыы", - "langCodeIso_639_2": "sah", - "countryCode": "ru", - "slug": "sah", - "wpLocale": "sah", - "code": "sah", - "translated": 0 - }, - "ory": { - "name": "Oriya", - "nativeName": "ଓଡ଼ିଆ", - "langCodeIso_639_1": "or", - "langCodeIso_639_2": "ory", - "countryCode": "in", - "slug": "ory", - "facebookLocale": "or_IN", - "wpLocale": "ory", - "code": "ory", - "translated": 0 - }, - "fa_IR": { - "name": "Persian", - "nativeName": "فارسی", - "langCodeIso_639_1": "fa", - "langCodeIso_639_2": "fas", - "slug": "fa", - "pluralExpression": "n > 1", - "googleCode": "fa", - "facebookLocale": "fa_IR", - "textDirection": "rtl", - "wpLocale": "fa_IR", - "code": "fa", - "translated": 0 - }, - "ps": { - "name": "Pashto", - "nativeName": "پښتو", - "langCodeIso_639_1": "ps", - "langCodeIso_639_2": "pus", - "countryCode": "af", - "slug": "ps", - "facebookLocale": "ps_AF", - "textDirection": "rtl", - "wpLocale": "ps", - "code": "ps", - "translated": 0 - }, - "pap_AW": { - "name": "Papiamento (Aruba)", - "nativeName": "Papiamento", - "langCodeIso_639_2": "pap", - "countryCode": "aw", - "slug": "pap-aw", - "wpLocale": "pap_AW", - "code": "pap-aw", - "translated": 0 - }, - "pap_CW": { - "name": "Papiamento (Curaçao and Bonaire)", - "nativeName": "Papiamentu", - "langCodeIso_639_2": "pap", - "countryCode": "cw", - "slug": "pap-cw", - "wpLocale": "pap_CW", - "code": "pap-cw", - "translated": 0 - }, - "art_xpirate": { - "name": "English (Pirate)", - "nativeName": "English (Pirate)", - "langCodeIso_639_2": "art", - "slug": "pirate", - "googleCode": "xx-pirate", - "facebookLocale": "en_PI", - "wpLocale": "art_xpirate", - "code": "pirate", - "translated": 0 - }, - "os": { - "name": "Ossetic", - "nativeName": "Ирон", - "langCodeIso_639_1": "os", - "langCodeIso_639_2": "oss", - "slug": "os", - "wpLocale": "os", - "code": "os", - "translated": 0 - }, - "sa_IN": { - "name": "Sanskrit", - "nativeName": "भारतम्", - "langCodeIso_639_1": "sa", - "langCodeIso_639_2": "san", - "countryCode": "in", - "slug": "sa-in", - "facebookLocale": "sa_IN", - "wpLocale": "sa_IN", - "code": "sa-in", - "translated": 0 - }, - "snd": { - "name": "Sindhi", - "nativeName": "سنڌي", - "langCodeIso_639_1": "sd", - "langCodeIso_639_2": "snd", - "countryCode": "pk", - "slug": "snd", - "textDirection": "rtl", - "wpLocale": "snd", - "code": "snd", - "translated": 0 - }, - "af": { - "name": "Afrikaans", - "nativeName": "Afrikaans", - "langCodeIso_639_1": "af", - "langCodeIso_639_2": "afr", - "countryCode": "za", - "slug": "af", - "googleCode": "af", - "facebookLocale": "af_ZA", - "wpLocale": "af", - "code": "af", - "translated": 0 - }, - "sk_SK": { - "name": "Slovak", - "nativeName": "Slovenčina", - "langCodeIso_639_1": "sk", - "langCodeIso_639_2": "slk", - "countryCode": "sk", - "slug": "sk", - "pluralExpression": "(n == 1) ? 0 : ((n >= 2 && n <= 4) ? 1 : 2)", - "googleCode": "sk", - "facebookLocale": "sk_SK", - "wpLocale": "sk_SK", - "code": "sk", - "translated": 0 - }, - "uk": { - "name": "Ukrainian", - "nativeName": "Українська", - "langCodeIso_639_1": "uk", - "langCodeIso_639_2": "ukr", - "countryCode": "ua", - "slug": "uk", - "pluralExpression": "(n % 10 == 1 && n % 100 != 11) ? 0 : ((n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14)) ? 1 : 2)", - "googleCode": "uk", - "facebookLocale": "uk_UA", - "wpLocale": "uk", - "code": "uk", - "translated": 0 - }, - "te": { - "name": "Telugu", - "nativeName": "తెలుగు", - "langCodeIso_639_1": "te", - "langCodeIso_639_2": "tel", - "slug": "te", - "googleCode": "te", - "facebookLocale": "te_IN", - "wpLocale": "te", - "code": "te", - "translated": 0 - }, - "th": { - "name": "Thai", - "nativeName": "ไทย", - "langCodeIso_639_1": "th", - "langCodeIso_639_2": "tha", - "slug": "th", - "pluralExpression": "0", - "googleCode": "th", - "facebookLocale": "th_TH", - "wpLocale": "th", - "code": "th", - "translated": 0 - }, - "bo": { - "name": "Tibetan", - "nativeName": "བོད་ཡིག", - "langCodeIso_639_1": "bo", - "langCodeIso_639_2": "tib", - "slug": "bo", - "pluralExpression": "0", - "wpLocale": "bo", - "code": "bo", - "translated": 0 - }, - "tir": { - "name": "Tigrinya", - "nativeName": "ትግርኛ", - "langCodeIso_639_1": "ti", - "langCodeIso_639_2": "tir", - "countryCode": "er", - "slug": "tir", - "pluralExpression": "0", - "wpLocale": "tir", - "code": "tir", - "translated": 0 - }, - "tr_TR": { - "name": "Turkish", - "nativeName": "Türkçe", - "langCodeIso_639_1": "tr", - "langCodeIso_639_2": "tur", - "countryCode": "tr", - "slug": "tr", - "pluralExpression": "n > 1", - "googleCode": "tr", - "facebookLocale": "tr_TR", - "wpLocale": "tr_TR", - "code": "tr", - "translated": 0 - }, - "tuk": { - "name": "Turkmen", - "nativeName": "Türkmençe", - "langCodeIso_639_1": "tk", - "langCodeIso_639_2": "tuk", - "countryCode": "tm", - "slug": "tuk", - "pluralExpression": "n > 1", - "facebookLocale": "tk_TM", - "wpLocale": "tuk", - "code": "tuk", - "translated": 0 - }, - "twd": { - "name": "Tweants", - "nativeName": "Twents", - "countryCode": "nl", - "slug": "twd", - "wpLocale": "twd", - "code": "twd", - "translated": 0 - }, - "ug_CN": { - "name": "Uighur", - "nativeName": "ئۇيغۇرچە", - "langCodeIso_639_1": "ug", - "langCodeIso_639_2": "uig", - "countryCode": "cn", - "slug": "ug", - "textDirection": "rtl", - "wpLocale": "ug_CN", - "code": "ug", - "translated": 0 - }, - "hsb": { - "name": "Upper Sorbian", - "nativeName": "Hornjoserbšćina", - "langCodeIso_639_2": "hsb", - "countryCode": "de", - "slug": "hsb", - "pluralExpression": "(n % 100 == 1) ? 0 : ((n % 100 == 2) ? 1 : ((n % 100 == 3 || n % 100 == 4) ? 2 : 3))", - "wpLocale": "hsb", - "code": "hsb", - "translated": 0 - }, - "ta_LK": { - "name": "Tamil (Sri Lanka)", - "nativeName": "தமிழ்", - "langCodeIso_639_1": "ta", - "langCodeIso_639_2": "tam", - "countryCode": "lk", - "slug": "ta-lk", - "googleCode": "ta", - "wpLocale": "ta_LK", - "code": "ta-lk", - "translated": 0 - }, - "ur": { - "name": "Urdu", - "nativeName": "اردو", - "langCodeIso_639_1": "ur", - "langCodeIso_639_2": "urd", - "countryCode": "pk", - "slug": "ur", - "googleCode": "ur", - "facebookLocale": "ur_PK", - "textDirection": "rtl", - "wpLocale": "ur", - "code": "ur", - "translated": 0 - }, - "uz_UZ": { - "name": "Uzbek", - "nativeName": "O‘zbekcha", - "langCodeIso_639_1": "uz", - "langCodeIso_639_2": "uzb", - "countryCode": "uz", - "slug": "uz", - "pluralExpression": "0", - "googleCode": "uz", - "facebookLocale": "uz_UZ", - "wpLocale": "uz_UZ", - "code": "uz", - "translated": 0 - }, - "vec": { - "name": "Venetian", - "nativeName": "Vèneto", - "langCodeIso_639_2": "roa", - "countryCode": "it", - "slug": "vec", - "wpLocale": "vec", - "code": "vec", - "translated": 0 - }, - "vi": { - "name": "Vietnamese", - "nativeName": "Tiếng Việt", - "langCodeIso_639_1": "vi", - "langCodeIso_639_2": "vie", - "countryCode": "vn", - "slug": "vi", - "pluralExpression": "0", - "googleCode": "vi", - "facebookLocale": "vi_VN", - "wpLocale": "vi", - "code": "vi", - "translated": 0 - }, - "cy": { - "name": "Welsh", - "nativeName": "Cymraeg", - "langCodeIso_639_1": "cy", - "langCodeIso_639_2": "cym", - "countryCode": "gb", - "slug": "cy", - "pluralExpression": "(n==1) ? 0 : (n==2) ? 1 : (n != 8 && n != 11) ? 2 : 3", - "googleCode": "cy", - "facebookLocale": "cy_GB", - "wpLocale": "cy", - "code": "cy", - "translated": 0 - }, - "wol": { - "name": "Wolof", - "nativeName": "Wolof", - "langCodeIso_639_1": "wo", - "langCodeIso_639_2": "wol", - "countryCode": "sn", - "slug": "wol", - "pluralExpression": "0", - "wpLocale": "wol", - "code": "wol", - "translated": 0 - }, - "xho": { - "name": "Xhosa", - "nativeName": "isiXhosa", - "langCodeIso_639_1": "xh", - "langCodeIso_639_2": "xho", - "countryCode": "za", - "slug": "xho", - "googleCode": "xh", - "facebookLocale": "xh_ZA", - "wpLocale": "xho", - "code": "xho", - "translated": 0 - }, - "yor": { - "name": "Yoruba", - "nativeName": "Yorùbá", - "langCodeIso_639_1": "yo", - "langCodeIso_639_2": "yor", - "countryCode": "ng", - "slug": "yor", - "googleCode": "yo", - "facebookLocale": "yo_NG", - "wpLocale": "yor", - "code": "yor", - "translated": 0 - }, - "tt_RU": { - "name": "Tatar", - "nativeName": "Татар теле", - "langCodeIso_639_1": "tt", - "langCodeIso_639_2": "tat", - "countryCode": "ru", - "slug": "tt", - "pluralExpression": "0", - "facebookLocale": "tt_RU", - "wpLocale": "tt_RU", - "code": "tt", - "translated": 0 - }, - "ta_IN": { - "name": "Tamil", - "nativeName": "தமிழ்", - "langCodeIso_639_1": "ta", - "langCodeIso_639_2": "tam", - "countryCode": "in", - "slug": "ta", - "googleCode": "ta", - "facebookLocale": "ta_IN", - "wpLocale": "ta_IN", - "code": "ta", - "translated": 0 - }, - "sl_SI": { - "name": "Slovenian", - "nativeName": "Slovenščina", - "langCodeIso_639_1": "sl", - "langCodeIso_639_2": "slv", - "countryCode": "si", - "slug": "sl", - "pluralExpression": "(n % 100 == 1) ? 0 : ((n % 100 == 2) ? 1 : ((n % 100 == 3 || n % 100 == 4) ? 2 : 3))", - "googleCode": "sl", - "facebookLocale": "sl_SI", - "wpLocale": "sl_SI", - "code": "sl", - "translated": 0 - }, - "es_PR": { - "name": "Spanish (Puerto Rico)", - "nativeName": "Español de Puerto Rico", - "langCodeIso_639_1": "es", - "langCodeIso_639_2": "spa", - "countryCode": "pr", - "slug": "es-pr", - "googleCode": "es", - "facebookLocale": "es_LA", - "wpLocale": "es_PR", - "code": "es-pr", - "translated": 0 - }, - "so_SO": { - "name": "Somali", - "nativeName": "Afsoomaali", - "langCodeIso_639_1": "so", - "langCodeIso_639_2": "som", - "countryCode": "so", - "slug": "so", - "googleCode": "so", - "facebookLocale": "so_SO", - "wpLocale": "so_SO", - "code": "so", - "translated": 0 - }, - "azb": { - "name": "South Azerbaijani", - "nativeName": "گؤنئی آذربایجان", - "langCodeIso_639_1": "az", - "countryCode": "ir", - "slug": "azb", - "textDirection": "rtl", - "wpLocale": "azb", - "code": "azb", - "translated": 0 - }, - "es_CL": { - "name": "Spanish (Chile)", - "nativeName": "Español de Chile", - "langCodeIso_639_1": "es", - "langCodeIso_639_2": "spa", - "countryCode": "cl", - "slug": "es-cl", - "googleCode": "es", - "facebookLocale": "es_LA", - "wpLocale": "es_CL", - "code": "es-cl", - "translated": 0 - }, - "es_CR": { - "name": "Spanish (Costa Rica)", - "nativeName": "Español de Costa Rica", - "langCodeIso_639_1": "es", - "langCodeIso_639_2": "spa", - "countryCode": "cr", - "slug": "es-cr", - "googleCode": "es", - "facebookLocale": "es_LA", - "wpLocale": "es_CR", - "code": "es-cr", - "translated": 0 - }, - "me_ME": { - "name": "Montenegrin", - "nativeName": "Crnogorski jezik", - "countryCode": "me", - "slug": "me", - "pluralExpression": "(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)", - "wpLocale": "me_ME", - "code": "me", - "translated": 0 - }, - "es_GT": { - "name": "Spanish (Guatemala)", - "nativeName": "Español de Guatemala", - "langCodeIso_639_1": "es", - "langCodeIso_639_2": "spa", - "countryCode": "gt", - "slug": "es-gt", - "googleCode": "es", - "facebookLocale": "es_LA", - "wpLocale": "es_GT", - "code": "es-gt", - "translated": 0 - }, - "es_HN": { - "name": "Spanish (Honduras)", - "nativeName": "Español de Honduras", - "langCodeIso_639_1": "es", - "langCodeIso_639_2": "spa", - "countryCode": "hn", - "slug": "es-hn", - "googleCode": "es", - "facebookLocale": "es_LA", - "wpLocale": "es_HN", - "code": "es-hn", - "translated": 0 - }, - "es_PE": { - "name": "Spanish (Peru)", - "nativeName": "Español de Perú", - "langCodeIso_639_1": "es", - "langCodeIso_639_2": "spa", - "countryCode": "pe", - "slug": "es-pe", - "googleCode": "es", - "facebookLocale": "es_LA", - "wpLocale": "es_PE", - "code": "es-pe", - "translated": 0 - }, - "es_UY": { - "name": "Spanish (Uruguay)", - "nativeName": "Español de Uruguay", - "langCodeIso_639_1": "es", - "langCodeIso_639_2": "spa", - "countryCode": "uy", - "slug": "es-uy", - "googleCode": "es", - "facebookLocale": "es_LA", - "wpLocale": "es_UY", - "code": "es-uy", - "translated": 0 - }, - "tzm": { - "name": "Tamazight (Central Atlas)", - "nativeName": "ⵜⴰⵎⴰⵣⵉⵖⵜ", - "langCodeIso_639_2": "tzm", - "countryCode": "ma", - "slug": "tzm", - "pluralExpression": "n > 1", - "wpLocale": "tzm", - "code": "tzm", - "translated": 0 - }, - "su_ID": { - "name": "Sundanese", - "nativeName": "Basa Sunda", - "langCodeIso_639_1": "su", - "langCodeIso_639_2": "sun", - "countryCode": "id", - "slug": "su", - "pluralExpression": "0", - "googleCode": "su", - "wpLocale": "su_ID", - "code": "su", - "translated": 0 - }, - "sw": { - "name": "Swahili", - "nativeName": "Kiswahili", - "langCodeIso_639_1": "sw", - "langCodeIso_639_2": "swa", - "slug": "sw", - "googleCode": "sw", - "facebookLocale": "sw_KE", - "wpLocale": "sw", - "code": "sw", - "translated": 0 - }, - "ssw": { - "name": "Swati", - "nativeName": "SiSwati", - "langCodeIso_639_1": "ss", - "langCodeIso_639_2": "ssw", - "countryCode": "sz", - "slug": "ssw", - "wpLocale": "ssw", - "code": "ssw", - "translated": 0 - }, - "syr": { - "name": "Syriac", - "nativeName": "Syriac", - "countryCode": "iq", - "slug": "syr", - "wpLocale": "syr", - "code": "syr", - "translated": 0 - }, - "tl": { - "name": "Tagalog", - "nativeName": "Tagalog", - "langCodeIso_639_1": "tl", - "langCodeIso_639_2": "tgl", - "countryCode": "ph", - "slug": "tl", - "googleCode": "tl", - "facebookLocale": "tl_PH", - "wpLocale": "tl", - "code": "tl", - "translated": 0 - }, - "tah": { - "name": "Tahitian", - "nativeName": "Reo Tahiti", - "langCodeIso_639_1": "ty", - "langCodeIso_639_2": "tah", - "countryCode": "pf", - "slug": "tah", - "pluralExpression": "n > 1", - "wpLocale": "tah", - "code": "tah", - "translated": 0 - }, - "tg": { - "name": "Tajik", - "nativeName": "Тоҷикӣ", - "langCodeIso_639_1": "tg", - "langCodeIso_639_2": "tgk", - "countryCode": "tj", - "slug": "tg", - "googleCode": "tg", - "facebookLocale": "tg_TJ", - "wpLocale": "tg", - "code": "tg", - "translated": 0 - }, - "zgh": { - "name": "Tamazight", - "nativeName": "ⵜⴰⵎⴰⵣⵉⵖⵜ", - "langCodeIso_639_2": "zgh", - "countryCode": "ma", - "slug": "zgh", - "pluralExpression": "n >= 2 && (n < 11 || n > 99)", - "wpLocale": "zgh", - "code": "zgh", - "translated": 0 - }, - "ary": { - "name": "Moroccan Arabic", - "nativeName": "العربية المغربية", - "langCodeIso_639_1": "ar", - "countryCode": "ma", - "slug": "ary", - "pluralExpression": "(n == 0) ? 0 : ((n == 1) ? 1 : ((n == 2) ? 2 : ((n % 100 >= 3 && n % 100 <= 10) ? 3 : ((n % 100 >= 11 && n % 100 <= 99) ? 4 : 5))))", - "textDirection": "rtl", - "wpLocale": "ary", - "code": "ary", - "translated": 0 - }, - "lug": { - "name": "Luganda", - "nativeName": "Oluganda", - "langCodeIso_639_1": "lg", - "langCodeIso_639_2": "lug", - "countryCode": "ug", - "slug": "lug", - "wpLocale": "lug", - "code": "lug", - "translated": 0 - }, - "mn": { - "name": "Mongolian", - "nativeName": "Монгол", - "langCodeIso_639_1": "mn", - "langCodeIso_639_2": "mon", - "countryCode": "mn", - "slug": "mn", - "googleCode": "mn", - "facebookLocale": "mn_MN", - "wpLocale": "mn", - "code": "mn", - "translated": 0 - }, - "art_xemoji": { - "name": "Emoji", - "langCodeIso_639_2": "art", - "slug": "art-xemoji", - "pluralExpression": "0", - "wpLocale": "art_xemoji", - "code": "art-xemoji", - "translated": 0 - }, - "ceb": { - "name": "Cebuano", - "nativeName": "Cebuano", - "langCodeIso_639_2": "ceb", - "countryCode": "ph", - "slug": "ceb", - "facebookLocale": "cx_PH", - "wpLocale": "ceb", - "code": "ceb", - "translated": 0 - }, - "zh_HK": { - "name": "Chinese (Hong Kong)", - "nativeName": "香港中文版\t", - "langCodeIso_639_1": "zh", - "langCodeIso_639_2": "zho", - "countryCode": "hk", - "slug": "zh-hk", - "pluralExpression": "0", - "facebookLocale": "zh_HK", - "wpLocale": "zh_HK", - "code": "zh-hk", - "translated": 0 - }, - "zh_SG": { - "name": "Chinese (Singapore)", - "nativeName": "中文", - "langCodeIso_639_1": "zh", - "langCodeIso_639_2": "zho", - "countryCode": "sg", - "slug": "zh-sg", - "pluralExpression": "0", - "wpLocale": "zh_SG", - "code": "zh-sg", - "translated": 0 - }, - "cor": { - "name": "Cornish", - "nativeName": "Kernewek", - "langCodeIso_639_1": "kw", - "langCodeIso_639_2": "cor", - "countryCode": "gb", - "slug": "cor", - "pluralExpression": "(n == 0) ? 0 : ((n == 1) ? 1 : (((n % 100 == 2 || n % 100 == 22 || n % 100 == 42 || n % 100 == 62 || n % 100 == 82) || n % 1000 == 0 && (n % 100000 >= 1000 && n % 100000 <= 20000 || n % 100000 == 40000 || n % 100000 == 60000 || n % 100000 == 80000) || n != 0 && n % 1000000 == 100000) ? 2 : ((n % 100 == 3 || n % 100 == 23 || n % 100 == 43 || n % 100 == 63 || n % 100 == 83) ? 3 : ((n != 1 && (n % 100 == 1 || n % 100 == 21 || n % 100 == 41 || n % 100 == 61 || n % 100 == 81)) ? 4 : 5))))", - "wpLocale": "cor", - "code": "cor", - "translated": 0 - }, - "co": { - "name": "Corsican", - "nativeName": "Corsu", - "langCodeIso_639_1": "co", - "langCodeIso_639_2": "cos", - "countryCode": "it", - "slug": "co", - "wpLocale": "co", - "code": "co", - "translated": 0 - }, - "hr": { - "name": "Croatian", - "nativeName": "Hrvatski", - "langCodeIso_639_1": "hr", - "langCodeIso_639_2": "hrv", - "countryCode": "hr", - "slug": "hr", - "pluralExpression": "(n % 10 == 1 && n % 100 != 11) ? 0 : ((n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14)) ? 1 : 2)", - "googleCode": "hr", - "facebookLocale": "hr_HR", - "wpLocale": "hr", - "code": "hr", - "translated": 0 - }, - "dv": { - "name": "Dhivehi", - "nativeName": "ދިވެހި", - "langCodeIso_639_1": "dv", - "langCodeIso_639_2": "div", - "countryCode": "mv", - "slug": "dv", - "textDirection": "rtl", - "wpLocale": "dv", - "code": "dv", - "translated": 0 - }, - "dzo": { - "name": "Dzongkha", - "nativeName": "རྫོང་ཁ", - "langCodeIso_639_1": "dz", - "langCodeIso_639_2": "dzo", - "countryCode": "bt", - "slug": "dzo", - "pluralExpression": "0", - "wpLocale": "dzo", - "code": "dzo", - "translated": 0 - }, - "en_NZ": { - "name": "English (New Zealand)", - "nativeName": "English (New Zealand)", - "langCodeIso_639_1": "en", - "langCodeIso_639_2": "eng", - "countryCode": "nz", - "slug": "en-nz", - "googleCode": "en", - "wpLocale": "en_NZ", - "code": "en-nz", - "translated": 0 - }, - "ca": { - "name": "Catalan", - "nativeName": "Català", - "langCodeIso_639_1": "ca", - "langCodeIso_639_2": "cat", - "slug": "ca", - "googleCode": "ca", - "facebookLocale": "ca_ES", - "wpLocale": "ca", - "code": "ca", - "translated": 0 - }, - "eo": { - "name": "Esperanto", - "nativeName": "Esperanto", - "langCodeIso_639_1": "eo", - "langCodeIso_639_2": "epo", - "slug": "eo", - "googleCode": "eo", - "facebookLocale": "eo_EO", - "wpLocale": "eo", - "code": "eo", - "translated": 0 - }, - "et": { - "name": "Estonian", - "nativeName": "Eesti", - "langCodeIso_639_1": "et", - "langCodeIso_639_2": "est", - "countryCode": "ee", - "slug": "et", - "googleCode": "et", - "facebookLocale": "et_EE", - "wpLocale": "et", - "code": "et", - "translated": 0 - }, - "fo": { - "name": "Faroese", - "nativeName": "Føroyskt", - "langCodeIso_639_1": "fo", - "langCodeIso_639_2": "fao", - "countryCode": "fo", - "slug": "fo", - "facebookLocale": "fo_FO", - "wpLocale": "fo", - "code": "fo", - "translated": 0 - }, - "fi": { - "name": "Finnish", - "nativeName": "Suomi", - "langCodeIso_639_1": "fi", - "langCodeIso_639_2": "fin", - "countryCode": "fi", - "slug": "fi", - "googleCode": "fi", - "facebookLocale": "fi_FI", - "wpLocale": "fi", - "code": "fi", - "translated": 0 - }, - "fon": { - "name": "Fon", - "nativeName": "fɔ̀ngbè", - "langCodeIso_639_2": "fon", - "countryCode": "bj", - "slug": "fon", - "wpLocale": "fon", - "code": "fon", - "translated": 0 - }, - "fr_BE": { - "name": "French (Belgium)", - "nativeName": "Français de Belgique", - "langCodeIso_639_1": "fr", - "langCodeIso_639_2": "fra", - "countryCode": "be", - "slug": "fr-be", - "pluralExpression": "n > 1", - "wpLocale": "fr_BE", - "code": "fr-be", - "translated": 0 - }, - "fr_CA": { - "name": "French (Canada)", - "nativeName": "Français du Canada", - "langCodeIso_639_1": "fr", - "langCodeIso_639_2": "fra", - "countryCode": "ca", - "slug": "fr-ca", - "pluralExpression": "n > 1", - "facebookLocale": "fr_CA", - "wpLocale": "fr_CA", - "code": "fr-ca", - "translated": 0 - }, - "fy": { - "name": "Frisian", - "nativeName": "Frysk", - "langCodeIso_639_1": "fy", - "langCodeIso_639_2": "fry", - "countryCode": "nl", - "slug": "fy", - "facebookLocale": "fy_NL", - "wpLocale": "fy", - "code": "fy", - "translated": 0 - }, - "bal": { - "name": "Catalan (Balear)", - "nativeName": "Català (Balear)", - "langCodeIso_639_2": "bal", - "countryCode": "es", - "slug": "bal", - "wpLocale": "bal", - "code": "bal", - "translated": 0 - }, - "bg_BG": { - "name": "Bulgarian", - "nativeName": "Български", - "langCodeIso_639_1": "bg", - "langCodeIso_639_2": "bul", - "countryCode": "bg", - "slug": "bg", - "googleCode": "bg", - "facebookLocale": "bg_BG", - "wpLocale": "bg_BG", - "code": "bg", - "translated": 0 - }, - "fuc": { - "name": "Fulah", - "nativeName": "Pulaar", - "langCodeIso_639_1": "ff", - "langCodeIso_639_2": "fuc", - "countryCode": "sn", - "slug": "fuc", - "wpLocale": "fuc", - "code": "fuc", - "translated": 0 - }, - "az_TR": { - "name": "Azerbaijani (Turkey)", - "nativeName": "Azərbaycan Türkcəsi", - "langCodeIso_639_1": "az", - "langCodeIso_639_2": "aze", - "countryCode": "tr", - "slug": "az-tr", - "wpLocale": "az_TR", - "code": "az-tr", - "translated": 0 - }, - "arq": { - "name": "Algerian Arabic", - "nativeName": "الدارجة الجزايرية", - "langCodeIso_639_1": "ar", - "countryCode": "dz", - "slug": "arq", - "pluralExpression": "(n == 0) ? 0 : ((n == 1) ? 1 : ((n == 2) ? 2 : ((n % 100 >= 3 && n % 100 <= 10) ? 3 : ((n % 100 >= 11 && n % 100 <= 99) ? 4 : 5))))", - "textDirection": "rtl", - "wpLocale": "arq", - "code": "arq", - "translated": 0 - }, - "am": { - "name": "Amharic", - "nativeName": "አማርኛ", - "langCodeIso_639_1": "am", - "langCodeIso_639_2": "amh", - "countryCode": "et", - "slug": "am", - "facebookLocale": "am_ET", - "wpLocale": "am", - "code": "am", - "translated": 0 - }, - "arg": { - "name": "Aragonese", - "nativeName": "Aragonés", - "langCodeIso_639_1": "an", - "langCodeIso_639_2": "arg", - "countryCode": "es", - "slug": "an", - "wpLocale": "arg", - "code": "an", - "translated": 0 - }, - "hy": { - "name": "Armenian", - "nativeName": "Հայերեն", - "langCodeIso_639_1": "hy", - "langCodeIso_639_2": "hye", - "countryCode": "am", - "slug": "hy", - "googleCode": "hy", - "facebookLocale": "hy_AM", - "wpLocale": "hy", - "code": "hy", - "translated": 0 - }, - "frp": { - "name": "Arpitan", - "nativeName": "Arpitan", - "countryCode": "fr", - "slug": "frp", - "pluralExpression": "n > 1", - "wpLocale": "frp", - "code": "frp", - "translated": 0 - }, - "as": { - "name": "Assamese", - "nativeName": "অসমীয়া", - "langCodeIso_639_1": "as", - "langCodeIso_639_2": "asm", - "countryCode": "in", - "slug": "as", - "facebookLocale": "as_IN", - "wpLocale": "as", - "code": "as", - "translated": 0 - }, - "ast": { - "name": "Asturian", - "nativeName": "Asturianu", - "langCodeIso_639_2": "ast", - "countryCode": "es", - "slug": "ast", - "wpLocale": "ast", - "code": "ast", - "translated": 0 - }, - "az": { - "name": "Azerbaijani", - "nativeName": "Azərbaycan dili", - "langCodeIso_639_1": "az", - "langCodeIso_639_2": "aze", - "countryCode": "az", - "slug": "az", - "googleCode": "az", - "facebookLocale": "az_AZ", - "wpLocale": "az", - "code": "az", - "translated": 0 - }, - "bcc": { - "name": "Balochi Southern", - "nativeName": "بلوچی مکرانی", - "countryCode": "pk", - "slug": "bcc", - "pluralExpression": "0", - "textDirection": "rtl", - "wpLocale": "bcc", - "code": "bcc", - "translated": 0 - }, - "bre": { - "name": "Breton", - "nativeName": "Brezhoneg", - "langCodeIso_639_1": "br", - "langCodeIso_639_2": "bre", - "countryCode": "fr", - "slug": "br", - "pluralExpression": "n > 1", - "facebookLocale": "br_FR", - "wpLocale": "bre", - "code": "br", - "translated": 0 - }, - "ba": { - "name": "Bashkir", - "nativeName": "башҡорт теле", - "langCodeIso_639_1": "ba", - "langCodeIso_639_2": "bak", - "slug": "ba", - "wpLocale": "ba", - "code": "ba", - "translated": 0 - }, - "eu": { - "name": "Basque", - "nativeName": "Euskara", - "langCodeIso_639_1": "eu", - "langCodeIso_639_2": "eus", - "countryCode": "es", - "slug": "eu", - "googleCode": "eu", - "facebookLocale": "eu_ES", - "wpLocale": "eu", - "code": "eu", - "translated": 0 - }, - "bel": { - "name": "Belarusian", - "nativeName": "Беларуская мова", - "langCodeIso_639_1": "be", - "langCodeIso_639_2": "bel", - "countryCode": "by", - "slug": "bel", - "pluralExpression": "(n % 10 == 1 && n % 100 != 11) ? 0 : ((n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14)) ? 1 : 2)", - "googleCode": "be", - "facebookLocale": "be_BY", - "wpLocale": "bel", - "code": "bel", - "translated": 0 - }, - "bn_BD": { - "name": "Bengali (Bangladesh)", - "nativeName": "বাংলা", - "langCodeIso_639_1": "bn", - "countryCode": "bd", - "slug": "bn", - "googleCode": "bn", - "wpLocale": "bn_BD", - "code": "bn", - "translated": 0 - }, - "bn_IN": { - "name": "Bengali (India)", - "nativeName": "বাংলা (ভারত)", - "langCodeIso_639_1": "bn", - "countryCode": "in", - "slug": "bn-in", - "pluralExpression": "n > 1", - "googleCode": "bn", - "facebookLocale": "bn_IN", - "wpLocale": "bn_IN", - "code": "bn-in", - "translated": 0 - }, - "bho": { - "name": "Bhojpuri", - "nativeName": "भोजपुरी", - "countryCode": "in", - "slug": "bho", - "wpLocale": "bho", - "code": "bho", - "translated": 0 - }, - "brx": { - "name": "Bodo", - "nativeName": "बोडो‎", - "countryCode": "in", - "slug": "brx", - "wpLocale": "brx", - "code": "brx", - "translated": 0 - }, - "gax": { - "name": "Borana-Arsi-Guji Oromo", - "nativeName": "Afaan Oromoo", - "countryCode": "et", - "slug": "gax", - "pluralExpression": "n > 1", - "wpLocale": "gax", - "code": "gax", - "translated": 0 - }, - "bs_BA": { - "name": "Bosnian", - "nativeName": "Bosanski", - "langCodeIso_639_1": "bs", - "langCodeIso_639_2": "bos", - "countryCode": "ba", - "slug": "bs", - "pluralExpression": "(n % 10 == 1 && n % 100 != 11) ? 0 : ((n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14)) ? 1 : 2)", - "googleCode": "bs", - "facebookLocale": "bs_BA", - "wpLocale": "bs_BA", - "code": "bs", - "translated": 0 - }, - "fur": { - "name": "Friulian", - "nativeName": "Friulian", - "langCodeIso_639_2": "fur", - "countryCode": "it", - "slug": "fur", - "wpLocale": "fur", - "code": "fur", - "translated": 0 - }, - "ka_GE": { - "name": "Georgian", - "nativeName": "ქართული", - "langCodeIso_639_1": "ka", - "langCodeIso_639_2": "kat", - "countryCode": "ge", - "slug": "ka", - "pluralExpression": "0", - "googleCode": "ka", - "facebookLocale": "ka_GE", - "wpLocale": "ka_GE", - "code": "ka", - "translated": 0 - }, - "mfe": { - "name": "Mauritian Creole", - "nativeName": "Kreol Morisien", - "countryCode": "mu", - "slug": "mfe", - "pluralExpression": "0", - "wpLocale": "mfe", - "code": "mfe", - "translated": 0 - }, - "lt_LT": { - "name": "Lithuanian", - "nativeName": "Lietuvių kalba", - "langCodeIso_639_1": "lt", - "langCodeIso_639_2": "lit", - "countryCode": "lt", - "slug": "lt", - "pluralExpression": "(n % 10 == 1 && (n % 100 < 11 || n % 100 > 19)) ? 0 : ((n % 10 >= 2 && n % 10 <= 9 && (n % 100 < 11 || n % 100 > 19)) ? 1 : 2)", - "googleCode": "lt", - "facebookLocale": "lt_LT", - "wpLocale": "lt_LT", - "code": "lt", - "translated": 0 - }, - "ckb": { - "name": "Kurdish (Sorani)", - "nativeName": "كوردی‎", - "langCodeIso_639_1": "ku", - "countryCode": "iq", - "slug": "ckb", - "facebookLocale": "cb_IQ", - "textDirection": "rtl", - "wpLocale": "ckb", - "code": "ckb", - "translated": 0 - }, - "kir": { - "name": "Kyrgyz", - "nativeName": "Кыргызча", - "langCodeIso_639_1": "ky", - "langCodeIso_639_2": "kir", - "countryCode": "kg", - "slug": "kir", - "pluralExpression": "0", - "googleCode": "ky", - "wpLocale": "kir", - "code": "kir", - "translated": 0 - }, - "lo": { - "name": "Lao", - "nativeName": "ພາສາລາວ", - "langCodeIso_639_1": "lo", - "langCodeIso_639_2": "lao", - "countryCode": "la", - "slug": "lo", - "pluralExpression": "0", - "googleCode": "lo", - "facebookLocale": "lo_LA", - "wpLocale": "lo", - "code": "lo", - "translated": 0 - }, - "lv": { - "name": "Latvian", - "nativeName": "Latviešu valoda", - "langCodeIso_639_1": "lv", - "langCodeIso_639_2": "lav", - "countryCode": "lv", - "slug": "lv", - "pluralExpression": "(n % 10 == 0 || n % 100 >= 11 && n % 100 <= 19) ? 0 : ((n % 10 == 1 && n % 100 != 11) ? 1 : 2)", - "googleCode": "lv", - "facebookLocale": "lv_LV", - "wpLocale": "lv", - "code": "lv", - "translated": 0 - }, - "lij": { - "name": "Ligurian", - "nativeName": "Lìgure", - "countryCode": "it", - "slug": "lij", - "wpLocale": "lij", - "code": "lij", - "translated": 0 - }, - "li": { - "name": "Limburgish", - "nativeName": "Limburgs", - "langCodeIso_639_1": "li", - "langCodeIso_639_2": "lim", - "countryCode": "nl", - "slug": "li", - "facebookLocale": "li_NL", - "wpLocale": "li", - "code": "li", - "translated": 0 - }, - "lin": { - "name": "Lingala", - "nativeName": "Ngala", - "langCodeIso_639_1": "ln", - "langCodeIso_639_2": "lin", - "countryCode": "cd", - "slug": "lin", - "pluralExpression": "n > 1", - "facebookLocale": "ln_CD", - "wpLocale": "lin", - "code": "lin", - "translated": 0 - }, - "lmo": { - "name": "Lombard", - "nativeName": "Lombardo", - "countryCode": "it", - "slug": "lmo", - "wpLocale": "lmo", - "code": "lmo", - "translated": 0 - }, - "ko_KR": { - "name": "Korean", - "nativeName": "한국어", - "langCodeIso_639_1": "ko", - "langCodeIso_639_2": "kor", - "countryCode": "kr", - "slug": "ko", - "pluralExpression": "0", - "googleCode": "ko", - "facebookLocale": "ko_KR", - "wpLocale": "ko_KR", - "code": "ko", - "translated": 0 - }, - "dsb": { - "name": "Lower Sorbian", - "nativeName": "Dolnoserbšćina", - "langCodeIso_639_2": "dsb", - "countryCode": "de", - "slug": "dsb", - "pluralExpression": "(n % 100 == 1) ? 0 : ((n % 100 == 2) ? 1 : ((n % 100 == 3 || n % 100 == 4) ? 2 : 3))", - "wpLocale": "dsb", - "code": "dsb", - "translated": 0 - }, - "lb_LU": { - "name": "Luxembourgish", - "nativeName": "Lëtzebuergesch", - "langCodeIso_639_1": "lb", - "countryCode": "lu", - "slug": "lb", - "wpLocale": "lb_LU", - "code": "lb", - "translated": 0 - }, - "mk_MK": { - "name": "Macedonian", - "nativeName": "Македонски јазик", - "langCodeIso_639_1": "mk", - "langCodeIso_639_2": "mkd", - "countryCode": "mk", - "slug": "mk", - "pluralExpression": "n % 10 != 1 || n % 100 == 11", - "googleCode": "mk", - "facebookLocale": "mk_MK", - "wpLocale": "mk_MK", - "code": "mk", - "translated": 0 - }, - "mai": { - "name": "Maithili", - "nativeName": "मैथिली", - "langCodeIso_639_2": "mai", - "countryCode": "in", - "slug": "mai", - "wpLocale": "mai", - "code": "mai", - "translated": 0 - }, - "mg_MG": { - "name": "Malagasy", - "nativeName": "Malagasy", - "langCodeIso_639_1": "mg", - "langCodeIso_639_2": "mlg", - "countryCode": "mg", - "slug": "mg", - "googleCode": "mg", - "facebookLocale": "mg_MG", - "wpLocale": "mg_MG", - "code": "mg", - "translated": 0 - }, - "ms_MY": { - "name": "Malay", - "nativeName": "Bahasa Melayu", - "langCodeIso_639_1": "ms", - "langCodeIso_639_2": "msa", - "slug": "ms", - "pluralExpression": "0", - "googleCode": "ms", - "facebookLocale": "ms_MY", - "wpLocale": "ms_MY", - "code": "ms", - "translated": 0 - }, - "ml_IN": { - "name": "Malayalam", - "nativeName": "മലയാളം", - "langCodeIso_639_1": "ml", - "langCodeIso_639_2": "mal", - "countryCode": "in", - "slug": "ml", - "googleCode": "ml", - "facebookLocale": "ml_IN", - "wpLocale": "ml_IN", - "code": "ml", - "translated": 0 - }, - "mri": { - "name": "Maori", - "nativeName": "Te Reo Māori", - "langCodeIso_639_1": "mi", - "countryCode": "nz", - "slug": "mri", - "pluralExpression": "n > 1", - "googleCode": "mi", - "wpLocale": "mri", - "code": "mri", - "translated": 0 - }, - "mr": { - "name": "Marathi", - "nativeName": "मराठी", - "langCodeIso_639_1": "mr", - "langCodeIso_639_2": "mar", - "slug": "mr", - "googleCode": "mr", - "facebookLocale": "mr_IN", - "wpLocale": "mr", - "code": "mr", - "translated": 0 - }, - "kmr": { - "name": "Kurdish (Kurmanji)", - "nativeName": "Kurdî", - "langCodeIso_639_1": "ku", - "countryCode": "tr", - "slug": "kmr", - "facebookLocale": "ku_TR", - "wpLocale": "kmr", - "code": "kmr", - "translated": 0 - }, - "kin": { - "name": "Kinyarwanda", - "nativeName": "Ikinyarwanda", - "langCodeIso_639_1": "rw", - "langCodeIso_639_2": "kin", - "countryCode": "rw", - "slug": "kin", - "facebookLocale": "rw_RW", - "wpLocale": "kin", - "code": "kin", - "translated": 0 - }, - "de_AT": { - "name": "German (Austria)", - "nativeName": "Deutsch (Österreich)", - "langCodeIso_639_1": "de", - "countryCode": "de", - "slug": "de-at", - "googleCode": "de", - "wpLocale": "de_AT", - "code": "de-at", - "translated": 0 - }, - "he_IL": { - "name": "Hebrew", - "nativeName": "עִבְרִית", - "langCodeIso_639_1": "he", - "countryCode": "il", - "slug": "he", - "googleCode": "iw", - "facebookLocale": "he_IL", - "textDirection": "rtl", - "wpLocale": "he_IL", - "code": "he", - "translated": 0 - }, - "de_CH": { - "name": "German (Switzerland)", - "nativeName": "Deutsch (Schweiz)", - "langCodeIso_639_1": "de", - "countryCode": "ch", - "slug": "de-ch", - "googleCode": "de", - "wpLocale": "de_CH", - "code": "de-ch", - "translated": 0 - }, - "el": { - "name": "Greek", - "nativeName": "Ελληνικά", - "langCodeIso_639_1": "el", - "langCodeIso_639_2": "ell", - "countryCode": "gr", - "slug": "el", - "googleCode": "el", - "facebookLocale": "el_GR", - "wpLocale": "el", - "code": "el", - "translated": 0 - }, - "kal": { - "name": "Greenlandic", - "nativeName": "Kalaallisut", - "langCodeIso_639_1": "kl", - "langCodeIso_639_2": "kal", - "countryCode": "gl", - "slug": "kal", - "wpLocale": "kal", - "code": "kal", - "translated": 0 - }, - "gu": { - "name": "Gujarati", - "nativeName": "ગુજરાતી", - "langCodeIso_639_1": "gu", - "langCodeIso_639_2": "guj", - "slug": "gu", - "googleCode": "gu", - "facebookLocale": "gu_IN", - "wpLocale": "gu", - "code": "gu", - "translated": 0 - }, - "hat": { - "name": "Haitian Creole", - "nativeName": "Kreyol ayisyen", - "langCodeIso_639_1": "ht", - "langCodeIso_639_2": "hat", - "countryCode": "ht", - "slug": "hat", - "wpLocale": "hat", - "code": "hat", - "translated": 0 - }, - "hau": { - "name": "Hausa", - "nativeName": "Harshen Hausa", - "langCodeIso_639_1": "ha", - "langCodeIso_639_2": "hau", - "countryCode": "ng", - "slug": "hau", - "googleCode": "ha", - "facebookLocale": "ha_NG", - "wpLocale": "hau", - "code": "hau", - "translated": 0 - }, - "haw_US": { - "name": "Hawaiian", - "nativeName": "Ōlelo Hawaiʻi", - "langCodeIso_639_2": "haw", - "countryCode": "us", - "slug": "haw", - "wpLocale": "haw_US", - "code": "haw", - "translated": 0 - }, - "haz": { - "name": "Hazaragi", - "nativeName": "هزاره گی", - "countryCode": "af", - "slug": "haz", - "textDirection": "rtl", - "wpLocale": "haz", - "code": "haz", - "translated": 0 - }, - "hi_IN": { - "name": "Hindi", - "nativeName": "हिन्दी", - "langCodeIso_639_1": "hi", - "langCodeIso_639_2": "hin", - "countryCode": "in", - "slug": "hi", - "googleCode": "hi", - "facebookLocale": "hi_IN", - "wpLocale": "hi_IN", - "code": "hi", - "translated": 0 - }, - "km": { - "name": "Khmer", - "nativeName": "ភាសាខ្មែរ", - "langCodeIso_639_1": "km", - "langCodeIso_639_2": "khm", - "countryCode": "kh", - "slug": "km", - "pluralExpression": "0", - "googleCode": "km", - "facebookLocale": "km_KH", - "wpLocale": "km", - "code": "km", - "translated": 0 - }, - "hu_HU": { - "name": "Hungarian", - "nativeName": "Magyar", - "langCodeIso_639_1": "hu", - "langCodeIso_639_2": "hun", - "countryCode": "hu", - "slug": "hu", - "googleCode": "hu", - "facebookLocale": "hu_HU", - "wpLocale": "hu_HU", - "code": "hu", - "translated": 0 - }, - "is_IS": { - "name": "Icelandic", - "nativeName": "Íslenska", - "langCodeIso_639_1": "is", - "langCodeIso_639_2": "isl", - "countryCode": "is", - "slug": "is", - "pluralExpression": "n % 10 != 1 || n % 100 == 11", - "googleCode": "is", - "facebookLocale": "is_IS", - "wpLocale": "is_IS", - "code": "is", - "translated": 0 - }, - "ido": { - "name": "Ido", - "nativeName": "Ido", - "langCodeIso_639_1": "io", - "langCodeIso_639_2": "ido", - "slug": "ido", - "wpLocale": "ido", - "code": "ido", - "translated": 0 - }, - "ibo": { - "name": "Igbo", - "nativeName": "Asụsụ Igbo", - "langCodeIso_639_1": "ig", - "langCodeIso_639_2": "ibo", - "countryCode": "ng", - "slug": "ibo", - "pluralExpression": "0", - "googleCode": "ig", - "wpLocale": "ibo", - "code": "ibo", - "translated": 0 - }, - "ga": { - "name": "Irish", - "nativeName": "Gaelige", - "langCodeIso_639_1": "ga", - "langCodeIso_639_2": "gle", - "countryCode": "ie", - "slug": "ga", - "pluralExpression": "(n == 1) ? 0 : ((n == 2) ? 1 : ((n >= 3 && n <= 6) ? 2 : ((n >= 7 && n <= 10) ? 3 : 4)))", - "googleCode": "ga", - "facebookLocale": "ga_IE", - "wpLocale": "ga", - "code": "ga", - "translated": 0 - }, - "jv_ID": { - "name": "Javanese", - "nativeName": "Basa Jawa", - "langCodeIso_639_1": "jv", - "langCodeIso_639_2": "jav", - "countryCode": "id", - "slug": "jv", - "googleCode": "jw", - "facebookLocale": "jv_ID", - "wpLocale": "jv_ID", - "code": "jv", - "translated": 0 - }, - "kab": { - "name": "Kabyle", - "nativeName": "Taqbaylit", - "langCodeIso_639_2": "kab", - "countryCode": "dz", - "slug": "kab", - "pluralExpression": "n > 1", - "wpLocale": "kab", - "code": "kab", - "translated": 0 - }, - "kaa": { - "name": "Karakalpak", - "nativeName": "Qaraqalpaq tili", - "langCodeIso_639_2": "kaa", - "countryCode": "uz", - "slug": "kaa", - "wpLocale": "kaa", - "code": "kaa", - "translated": 0 - }, - "kk": { - "name": "Kazakh", - "nativeName": "Қазақ тілі", - "langCodeIso_639_1": "kk", - "langCodeIso_639_2": "kaz", - "countryCode": "kz", - "slug": "kk", - "googleCode": "kk", - "facebookLocale": "kk_KZ", - "wpLocale": "kk", - "code": "kk", - "translated": 0 - }, - "zul": { - "name": "Zulu", - "nativeName": "isiZulu", - "langCodeIso_639_1": "zu", - "langCodeIso_639_2": "zul", - "countryCode": "za", - "slug": "zul", - "googleCode": "zu", - "wpLocale": "zul", - "code": "zul", - "translated": 0 - } -} diff --git a/src/locales/scripts/separate-download.js b/src/locales/scripts/separate-download.js new file mode 100644 index 0000000000..a4ca219afa --- /dev/null +++ b/src/locales/scripts/separate-download.js @@ -0,0 +1,57 @@ +const axios = require("./axios") +const { writeLocaleFile } = require("./utils") +const jed1xJsonToJson = require("./jed1x-json-to-json") + +const DOWNLOAD_BASE_URL = + "https://translate.wordpress.org/projects/meta/openverse" + +const getTranslationUrl = (locale) => + `${DOWNLOAD_BASE_URL}/${locale}/default/export-translations/` + +const fetchJed1x = async (locale) => { + try { + const res = await axios.get(getTranslationUrl(locale), { + params: { format: "jed1x" }, + }) + return res.data + } catch (err) { + return err.response.status + } +} + +const isEmpty = (obj) => Object.values(obj).every((x) => x === null) + +const fetchJed1xAll = async (locales) => { + const results = await Promise.allSettled(locales.map(fetchJed1x)) + + let succeeded = {} + let failed = {} + results.forEach(({ status, value }, index) => { + if (status === "fulfilled" && !isEmpty(value)) { + succeeded[locales[index]] = jed1xJsonToJson(value) + } else { + failed[locales[index]] = value + } + }) + await Promise.all( + Object.entries(succeeded).map((args) => writeLocaleFile(...args)) + ) + + return [Object.keys(succeeded).length, Object.keys(failed).length] +} + +const separateDownload = async () => { + console.log("Performing parallel download.") + + const localeJson = require("./wp-locales.json") + const locales = Object.values(localeJson).map((item) => item.slug) + const [succeeded, failed] = await fetchJed1xAll(locales) + + console.log(`Successfully downloaded ${succeeded} translations.`) + if (failed) { + console.log(`Failed to download ${failed} translations.`) + throw new Error("Parallel download partially failed") + } +} + +module.exports = separateDownload diff --git a/src/locales/scripts/utils.js b/src/locales/scripts/utils.js index 2efa74c861..f4943825b2 100644 --- a/src/locales/scripts/utils.js +++ b/src/locales/scripts/utils.js @@ -1,3 +1,6 @@ +const { writeFile } = require("fs/promises") +const os = require("os") + /** * Mutates an object at the path with the value. If the path * does not exist, it is created by nesting objects along the @@ -19,3 +22,40 @@ exports.setToValue = function setValue(obj, path, value) { } o[a[0]] = value } + +/** + * Replace ###### with {}. + * + * @param json {any} - the JSON object to replace placeholders in + * @return {any} the sanitised JSON object + */ +const replacePlaceholders = (json) => { + if (json === null) { + return null + } + if (typeof json === "string") { + return json.replace(/###([a-zA-Z-]*)###/g, "{$1}") + } + let currentJson = { ...json } + + for (const row of Object.entries(currentJson)) { + let [key, value] = row + currentJson[key] = replacePlaceholders(value) + } + return currentJson +} + +exports.replacePlaceholders = replacePlaceholders + +/** + * Write translation strings to a file in the locale directory + * @param {string} locale + * @param {any} rawTranslations + */ +exports.writeLocaleFile = (locale, rawTranslations) => { + const translations = replacePlaceholders(rawTranslations) + return writeFile( + process.cwd() + `/src/locales/${locale}.json`, + JSON.stringify(translations, null, 2) + os.EOL + ) +}