Streamline javascript translation by improving translationRunner (#2808)

* package.json: Add "build:*" targets

* Improve react-intl-translations-manager workflow.
  * Added "build:production" to build production bundle.
  * Added "build:development" to build development bundle.

* Fix json translation files

* Run `yarn manage:translations` to fix translation files.
* Fix `pl.json` for syntax error.

* translationRunner: auto detect existing languages

* Auto detect existing rfc5646 language tag in *.json filenames
  in `app/javascript/mastodon/locale` folder. No need to manually
  define every new language in the languages array here.

* translationRunner: add more functionality

* Allow script user to specify language code to check.
* Added available language check.
* Added --force flag to force creation of unexists language.
* Added --help flag and help messages.

* gitignore: ignore npm-debug.log

* Fix webpack error if NODE_ENV is not defined

Default to use 'development' in config/webpack/configuration.js
lolsob-rspec
Koala Yeung 2017-05-06 10:05:38 +08:00 committed by Eugen Rochko
parent 102c829f16
commit b1f14a1c7f
8 changed files with 101 additions and 41 deletions

5
.gitignore vendored
View File

@ -46,5 +46,10 @@ redis
/public/packs /public/packs
/node_modules /node_modules
# Ignore npm debug log
npm-debug.log
# Ignore Docker option files # Ignore Docker option files
docker-compose.override.yml docker-compose.override.yml

View File

@ -4,8 +4,8 @@
"account.edit_profile": "עריכת פרופיל", "account.edit_profile": "עריכת פרופיל",
"account.follow": "מעקב", "account.follow": "מעקב",
"account.followers": "עוקבים", "account.followers": "עוקבים",
"account.follows_you": "במעקב אחריך",
"account.follows": "נעקבים", "account.follows": "נעקבים",
"account.follows_you": "במעקב אחריך",
"account.mention": "אזכור של @{name}", "account.mention": "אזכור של @{name}",
"account.mute": "להשתיק את @{name}", "account.mute": "להשתיק את @{name}",
"account.posts": "הודעות", "account.posts": "הודעות",
@ -53,8 +53,9 @@
"emoji_button.travel": "טיולים ואתרים", "emoji_button.travel": "טיולים ואתרים",
"empty_column.community": "טור הסביבה ריק. יש לפרסם משהו כדי שדברים יתרחילו להתגלגל!", "empty_column.community": "טור הסביבה ריק. יש לפרסם משהו כדי שדברים יתרחילו להתגלגל!",
"empty_column.hashtag": "אין כלום בהאשתג הזה עדיין.", "empty_column.hashtag": "אין כלום בהאשתג הזה עדיין.",
"empty_column.home.public_timeline": "בפרהסיה",
"empty_column.home": "אף אחד לא במעקב עדיין. אפשר לבקר ב{public} או להשתמש בחיפוש כדי להתחיל ולהכיר חצוצרנים אחרים.", "empty_column.home": "אף אחד לא במעקב עדיין. אפשר לבקר ב{public} או להשתמש בחיפוש כדי להתחיל ולהכיר חצוצרנים אחרים.",
"empty_column.home.inactivity": "Your home feed is empty. If you have been inactive for a while, it will be regenerated for you soon.",
"empty_column.home.public_timeline": "בפרהסיה",
"empty_column.notifications": "אין התראות עדיין. יאללה, הגיע הזמן להתחיל להתערבב!", "empty_column.notifications": "אין התראות עדיין. יאללה, הגיע הזמן להתחיל להתערבב!",
"empty_column.public": "אין פה כלום! כדי למלא את הטור הזה אפשר לכתוב משהו, או להתחיל לעקוב אחרי אנשים מקהילות אחרות.", "empty_column.public": "אין פה כלום! כדי למלא את הטור הזה אפשר לכתוב משהו, או להתחיל לעקוב אחרי אנשים מקהילות אחרות.",
"follow_request.authorize": "קבלה", "follow_request.authorize": "קבלה",
@ -84,7 +85,6 @@
"navigation_bar.public_timeline": "בפרהסיה", "navigation_bar.public_timeline": "בפרהסיה",
"notification.favourite": "חצרוצך חובב על ידי {name}", "notification.favourite": "חצרוצך חובב על ידי {name}",
"notification.follow": "{name} במעקב אחרייך", "notification.follow": "{name} במעקב אחרייך",
"notification.mention": "אוזכרת ע\"י {name}",
"notification.reblog": "חצרוצך הודהד על ידי {name}", "notification.reblog": "חצרוצך הודהד על ידי {name}",
"notifications.clear": "הסרת התראות", "notifications.clear": "הסרת התראות",
"notifications.clear_confirmation": "להסיר את כל ההתראות? בטוח?", "notifications.clear_confirmation": "להסיר את כל ההתראות? בטוח?",
@ -131,7 +131,6 @@
"report.submit": "שליחה", "report.submit": "שליחה",
"report.target": "דיווח", "report.target": "דיווח",
"search.placeholder": "חיפוש", "search.placeholder": "חיפוש",
"search.status_by": "הודעה מאת {name}",
"search_results.total": "{count, number} {count, plural, one {תוצאה} other {תוצאות}}", "search_results.total": "{count, number} {count, plural, one {תוצאה} other {תוצאות}}",
"status.cannot_reblog": "לא ניתן להדהד הודעה זו", "status.cannot_reblog": "לא ניתן להדהד הודעה זו",
"status.delete": "מחיקה", "status.delete": "מחיקה",
@ -145,8 +144,8 @@
"status.reply": "תגובה", "status.reply": "תגובה",
"status.replyAll": "תגובה לכולם", "status.replyAll": "תגובה לכולם",
"status.report": "דיווח על @{name}", "status.report": "דיווח על @{name}",
"status.sensitive_warning": "תוכן רגיש",
"status.sensitive_toggle": "לחצו כדי לראות", "status.sensitive_toggle": "לחצו כדי לראות",
"status.sensitive_warning": "תוכן רגיש",
"status.show_less": "הראה פחות", "status.show_less": "הראה פחות",
"status.show_more": "הראה יותר", "status.show_more": "הראה יותר",
"tabs_bar.compose": "חיבור", "tabs_bar.compose": "חיבור",

View File

@ -53,8 +53,9 @@
"emoji_button.travel": "Podróże i miejsca", "emoji_button.travel": "Podróże i miejsca",
"empty_column.community": "Lokalna oś czasu jest pusta. Napisz coś publicznie, aby odbić piłeczkę!", "empty_column.community": "Lokalna oś czasu jest pusta. Napisz coś publicznie, aby odbić piłeczkę!",
"empty_column.hashtag": "Nie ma postów oznaczonych tym hashtagiem. Możesz napisać pierwszy!", "empty_column.hashtag": "Nie ma postów oznaczonych tym hashtagiem. Możesz napisać pierwszy!",
"empty_column.home.public_timeline": "publiczna oś czasu",
"empty_column.home": "Nie obserwujesz nikogo. Odwiedź publiczną oś czasu lub użyj wyszukiwarki, aby znaleźć ciekawych ludzi.", "empty_column.home": "Nie obserwujesz nikogo. Odwiedź publiczną oś czasu lub użyj wyszukiwarki, aby znaleźć ciekawych ludzi.",
"empty_column.home.inactivity": "Your home feed is empty. If you have been inactive for a while, it will be regenerated for you soon.",
"empty_column.home.public_timeline": "publiczna oś czasu",
"empty_column.notifications": "Nie masz żadnych powiadomień. Rozpocznij interakcje z innymi użytkownikami.", "empty_column.notifications": "Nie masz żadnych powiadomień. Rozpocznij interakcje z innymi użytkownikami.",
"empty_column.public": "Tu nic nie ma! Napisz coś publicznie, lub dodaj ludzi z innych instancji, aby to wyświetlić.", "empty_column.public": "Tu nic nie ma! Napisz coś publicznie, lub dodaj ludzi z innych instancji, aby to wyświetlić.",
"follow_request.authorize": "Autoryzuj", "follow_request.authorize": "Autoryzuj",
@ -84,7 +85,6 @@
"navigation_bar.public_timeline": "Oś czasu federacji", "navigation_bar.public_timeline": "Oś czasu federacji",
"notification.favourite": "{name} dodał twój status do ulubionych", "notification.favourite": "{name} dodał twój status do ulubionych",
"notification.follow": "{name} zaczął cię obserwować", "notification.follow": "{name} zaczął cię obserwować",
"notification.mention": "{name} wspomniał o tobie",
"notification.reblog": "{name} podbił twój status", "notification.reblog": "{name} podbił twój status",
"notifications.clear": "Wyczyść powiadomienia", "notifications.clear": "Wyczyść powiadomienia",
"notifications.clear_confirmation": "Czy na pewno chcesz bezpowrotnie usunąć wszystkie powiadomienia?", "notifications.clear_confirmation": "Czy na pewno chcesz bezpowrotnie usunąć wszystkie powiadomienia?",
@ -131,7 +131,6 @@
"report.submit": "Wyślij", "report.submit": "Wyślij",
"report.target": "Zgłaszanie", "report.target": "Zgłaszanie",
"search.placeholder": "Szukaj", "search.placeholder": "Szukaj",
"search.status_by": "Status od {name}",
"search_results.total": "{count, number} {count, plural, one {wynik} more {wyniki}}", "search_results.total": "{count, number} {count, plural, one {wynik} more {wyniki}}",
"status.cannot_reblog": "Ten post nie może zostać podbity", "status.cannot_reblog": "Ten post nie może zostać podbity",
"status.delete": "Usuń", "status.delete": "Usuń",
@ -161,5 +160,5 @@
"video_player.expand": "Przełącz wideo", "video_player.expand": "Przełącz wideo",
"video_player.toggle_sound": "Przełącz dźwięk", "video_player.toggle_sound": "Przełącz dźwięk",
"video_player.toggle_visible": "Przełącz widoczność", "video_player.toggle_visible": "Przełącz widoczność",
"video_player.video_error": "Nie można odtworzyć pliku wideo", "video_player.video_error": "Nie można odtworzyć pliku wideo"
}; }

View File

@ -0,0 +1,2 @@
[
]

View File

@ -0,0 +1,2 @@
[
]

View File

@ -7,8 +7,8 @@ const { readFileSync } = require('fs')
const configPath = resolve('config', 'webpack') const configPath = resolve('config', 'webpack')
const loadersDir = join(__dirname, 'loaders') const loadersDir = join(__dirname, 'loaders')
const paths = safeLoad(readFileSync(join(configPath, 'paths.yml'), 'utf8'))[env.NODE_ENV] const paths = safeLoad(readFileSync(join(configPath, 'paths.yml'), 'utf8'))[env.NODE_ENV || 'development']
const devServer = safeLoad(readFileSync(join(configPath, 'development.server.yml'), 'utf8'))[env.NODE_ENV] const devServer = safeLoad(readFileSync(join(configPath, 'development.server.yml'), 'utf8'))[env.NODE_ENV || 'development']
// Compute public path based on environment and CDN_HOST in production // Compute public path based on environment and CDN_HOST in production
const ifHasCDN = env.CDN_HOST !== undefined && env.NODE_ENV === 'production' const ifHasCDN = env.CDN_HOST !== undefined && env.NODE_ENV === 'production'

View File

@ -1,34 +1,84 @@
/*eslint no-console: "off"*/
const manageTranslations = require('react-intl-translations-manager').default; const manageTranslations = require('react-intl-translations-manager').default;
const argv = require('minimist')(process.argv.slice(2));
const fs = require('fs');
const translationsDirectory = 'app/javascript/mastodon/locales';
const localeFn = /^([a-z]{2,3}(|\-[A-Z]+))\.json$/;
const reRFC5646 = /^[a-z]{2,3}(|\-[A-Z]+)$/;
const availableLanguages = fs.readdirSync(`${process.cwd()}/${translationsDirectory}`).reduce((acc, fn) => {
if (fn.match(localeFn)) {
acc.push(fn.replace(localeFn, '$1'));
}
return acc;
}, []);
// print help message
if (argv.help !== undefined) {
console.log(
`Usage: yarn manage:translations -- [OPTIONS] [LANGUAGES]
Manage javascript translation files in mastodon. Generates and update
translations in translationsDirectory: ${translationsDirectory}
OPTIONS
--help show this message
--force force using the provided languages. create files if not exists.
default: false
LANGUAGES
The RFC5646 language tag for the language you want to test or fix. If you want
to input multiple languages, separate them with space.
Available languages:
${availableLanguages}
`);
process.exit(0);
}
// determine the languages list
const languages = (argv._.length === 0) ? availableLanguages : argv._;
// check if the languages provided are RFC5626 compliant
(function() {
let invalidLanguages = languages.reduce((acc, language) => {
if (!language.match(reRFC5646)) {
acc.push(language);
}
return acc;
}, []);
if (invalidLanguages.length > 0) {
console.log(`Error:`);
for (let language of invalidLanguages) {
console.error(`* Not RFC5626 name: ${language}`);
}
console.log(`\nUse yarn "manage:translations -- --help" for usage information\n`);
process.exit(1);
}
})();
// make sure the language exists. Unless force to create locale file.
if (argv.force !== true) {
let invalidLanguages = languages.reduce((acc, language) => {
if (availableLanguages.indexOf(language) < 0) {
acc.push(language);
}
return acc;
}, []);
if (invalidLanguages.length > 0) {
console.log(`Error:`);
for (let language of invalidLanguages) {
console.error(`* Language not available: ${language}`);
}
console.log(`\nIf you want to force creating the language(s) above, please add the --force option.\n`);
process.exit(1);
}
}
manageTranslations({ manageTranslations({
messagesDirectory: 'build/messages', messagesDirectory: 'build/messages',
translationsDirectory: 'app/javascript/mastodon/locales/', translationsDirectory,
detectDuplicateIds: false, detectDuplicateIds: false,
singleMessagesFile: true, singleMessagesFile: true,
languages: [ languages,
'ar', });
'en',
'de',
'es',
'fa',
'hr',
'hu',
'io',
'it',
'fr',
'nl',
'no',
'oc',
'pt',
'pt-BR',
'uk',
'fi',
'eo',
'ru',
'ja',
'zh-HK',
'zh-CN',
'bg',
'id',
],
})

View File

@ -3,6 +3,8 @@
"license": "AGPL-3.0", "license": "AGPL-3.0",
"scripts": { "scripts": {
"postversion": "git push --tags", "postversion": "git push --tags",
"build:development": "NODE_ENV=development yarn webpack -- --config config/webpack/development.js",
"build:production": "NODE_ENV=production yarn webpack -- --config config/webpack/production.js",
"manage:translations": "node ./config/webpack/translationRunner.js", "manage:translations": "node ./config/webpack/translationRunner.js",
"start": "babel-node ./streaming/index.js --presets es2015,stage-2", "start": "babel-node ./streaming/index.js --presets es2015,stage-2",
"storybook": "start-storybook -p 9001 -c storybook", "storybook": "start-storybook -p 9001 -c storybook",
@ -113,6 +115,7 @@
"eslint-plugin-jsx-a11y": "^4.0.0", "eslint-plugin-jsx-a11y": "^4.0.0",
"eslint-plugin-react": "^6.10.3", "eslint-plugin-react": "^6.10.3",
"jsdom": "^9.11.0", "jsdom": "^9.11.0",
"minimist": "^1.2.0",
"mocha": "^3.2.0", "mocha": "^3.2.0",
"react-intl-translations-manager": "^5.0.0", "react-intl-translations-manager": "^5.0.0",
"webpack-dev-server": "^2.4.5" "webpack-dev-server": "^2.4.5"