diff --git a/i18n/README.md b/i18n/README.md index 84d813b0..2056e3b8 100644 --- a/i18n/README.md +++ b/i18n/README.md @@ -2,6 +2,8 @@ The easiets way to help translate the game is with weblate. Simply use [hosted.weblate.org/projects/pixelplanet](https://hosted.weblate.org/projects/pixelplanet/). Feel free to ask in the Translation section in [our Discord](https://pixelplanet.fun/guilded) if you need help. +If a language code differs from the country code of a wanted flag, it can be defined in the `i18n/lccc.json` file. In example `{ "en": "gb" }` maps the english language to the flag of Great Britain. + [![Translation status](https://hosted.weblate.org/widget/pixelplanet/open-graph.png)](https://hosted.weblate.org/engage/pixelplanet/) # Translating Offline @@ -12,8 +14,6 @@ Translation files can be created out of the templates [template.pot](https://git All translated languages get an own chat channel that just people who use this language can access. -If a language code differs from the country code of a wanted flag, it can be defined in the ssr filename, like `ssr-en-gb.po` would be the english language, with the flag of Great Britain. - ## With poedit ### Create new translation diff --git a/scripts/build.js b/scripts/build.js index 294cf7b8..35c04fe1 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -5,13 +5,13 @@ const path = require('path'); const fs = require('fs'); +const readline = require('readline'); const { spawn } = require('child_process'); const webpack = require('webpack'); const minifyCss = require('./minifyCss'); const serverConfig = require('../webpack.config.server.js'); const clientConfig = require('../webpack.config.client.js'); -const { getAllAvailableLocals } = clientConfig; let langs = 'all'; let doBuildServer = false; @@ -46,6 +46,88 @@ if (!doBuildServer && !doBuildClient) { doBuildClient = true; } +/* + * get available locals based on the files available in ../i18n + */ +function getAllAvailableLocals() { + const langDir = path.resolve(__dirname, '..', 'i18n'); + const langs = fs.readdirSync(langDir) + .filter((e) => (e.endsWith('.po') && !e.startsWith('ssr'))) + .map((l) => l.slice(0, -3)); + langs.unshift('en'); + return langs; +} + +/* + * get amount of msgid and msgstr of po file + */ +function getPoFileStats(file) { + return new Promise((resolve) => { + const fileStream = fs.createReadStream(file); + const lineReader = readline.createInterface({ + input: fileStream, + crlfDelay: Infinity, + }); + + let msgid = 0; + let msgstr = 0; + + lineReader.on('line', (l) => { + l = l.trim(); + if (l.endsWith('""')) { + return; + } + let seperator = l.indexOf(' '); + if (seperator === -1) { + seperator = l.indexOf('\t'); + } + if (seperator === -1) { + return; + } + const tag = l.substring(0, seperator); + if (tag === 'msgid') { + msgid += 1; + } else if (tag === 'msgstr') { + msgstr += 1; + } + }); + + lineReader.on('close', (l) => { + resolve({ msgid, msgstr }); + }); + }); +} + +async function filterLackingLocals(langs, percentage) { + langs = langs.filter((l) => l !== 'en'); + const promises = []; + const { msgid, msgstr } = await getPoFileStats(path.resolve( + __dirname, '..', 'i18n', `template.pot`, + )); + + const langStats = await Promise.all(langs + .map((l) => getPoFileStats( + path.resolve(__dirname, '..', 'i18n', `${l}.po`), + ))); + const goodLangs = [ 'en' ]; + const badLangs = []; + for (let i = 0; i < langs.length; i += 1) { + const lang = langs[i]; + const stats = langStats[i]; + const percent = Math.floor(stats.msgstr / msgid * 100); + if (percent >= percentage) { + goodLangs.push(lang); + } else { + console.log(`Lang ${lang} completion:`, percent, '%'); + badLangs.push(lang); + } + } + return { + goodLangs, + badLangs, + }; +} + function compile(webpackConfig) { return new Promise((resolve, reject) => { webpack(webpackConfig, (err, stats) => { @@ -156,13 +238,23 @@ async function buildProduction() { if (langs !== 'all') { avlangs = langs.split(',').map((l) => l.trim()) .filter((l) => avlangs.includes(l)); - if (!avlangs.length) { - console.error(`ERROR: language ${langs} not available`); - process.exit(1); - return; + } else { + const { avlangs: goodLangs, badLangs } = await filterLackingLocals(avlangs, 50); + if (badLangs.length) { + console.log( + 'Skipping', + badLangs.length, + 'locals because of low completition:', + badLangs, + ); } } - console.log('Building locales:', avlangs); + if (!avlangs.length) { + console.error(`ERROR: language ${langs} not available`); + process.exit(1); + return; + } + console.log('Building', avlangs.length, 'locales:', avlangs); const promises = []; diff --git a/src/core/ttag.js b/src/core/ttag.js index b9354712..60d4c520 100644 --- a/src/core/ttag.js +++ b/src/core/ttag.js @@ -6,6 +6,7 @@ import cookie from 'cookie'; import assetWatcher from './fsWatcher'; import { getLangsOfJsAsset } from './assets'; +import lccc from '../../i18n/lccc.json'; // eslint-disable-next-line max-len const localeImports = require.context('../../i18n', false, /^\.[/\\]ssr-.+\.po$/); @@ -32,16 +33,11 @@ function loadTtags() { const file = langs[i]; // ./ssr-de.po let lang = file.replace('./ssr-', '').replace('.po', '').toLowerCase(); - let flag = lang; /* - * in cases where the language code and country code differ, - * the country code can be given seperately in the file name - * i.e.: ./ssr-en-gb.po + * In cases where the language code and country code differ, + * it can be mapped in i18n/lccc.json */ - const seperator = lang.indexOf('-'); - if (seperator !== -1) { - [lang, flag] = lang.split('-'); - } + let flag = lccc[lang] || lang; if (jsLangs.includes(lang)) { if (!ttags[lang]) { const ttag = new TTag(); diff --git a/webpack.config.client.js b/webpack.config.client.js index 08d85adf..e2d65259 100644 --- a/webpack.config.client.js +++ b/webpack.config.client.js @@ -183,13 +183,3 @@ module.exports = ({ }; } -function getAllAvailableLocals() { - const langDir = path.resolve('i18n'); - const langs = fs.readdirSync(langDir) - .filter((e) => (e.endsWith('.po') && !e.startsWith('ssr'))) - .map((l) => l.slice(0, -3)); - langs.unshift('en'); - return langs; -} - -module.exports.getAllAvailableLocals = getAllAvailableLocals;