From 205a6c157c67a9ca906f3c7934c53db6f0a4a3fa Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Wed, 16 Nov 2022 21:40:46 +0800 Subject: [PATCH 1/2] MDL-76331 editor_tiny: Update language strings to use shorter ident AMOS can only cope with string identifiers with a length < 100 characters. Our tooling for Tiny automatically generates string idents based on the language string and these can end up longer. This change modifies our generated idents to be capped at 90 characters. --- lib/editor/tiny/lang/en/editor_tiny.php | 14 +++++++------- lib/editor/tiny/tinystrings.json | 14 +++++++------- lib/editor/tiny/tools/getOriginals.mjs | 7 +++++++ 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/lib/editor/tiny/lang/en/editor_tiny.php b/lib/editor/tiny/lang/en/editor_tiny.php index 0767a54a025..723035aa213 100644 --- a/lib/editor/tiny/lang/en/editor_tiny.php +++ b/lib/editor/tiny/lang/en/editor_tiny.php @@ -183,7 +183,7 @@ $string['tiny:horizontal_align'] = 'Horizontal align'; $string['tiny:horizontal_line'] = 'Horizontal line'; $string['tiny:horizontal_space'] = 'Horizontal space'; $string['tiny:id'] = 'ID'; -$string['tiny:id_should_start_with_a_letter_followed_only_by_letters_numbers_dashes_dots_colons_or_underscores.'] = 'ID should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.'; +$string['tiny:id_should_start_with_a_letter_followed_only_by_letters_numbers_dashes_dots_colons_or_'] = 'ID should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.'; $string['tiny:image_is_decorative'] = 'Image is decorative'; $string['tiny:image_list'] = 'Image list'; $string['tiny:image_title'] = 'Image title'; @@ -274,7 +274,7 @@ $string['tiny:paste'] = 'Paste'; $string['tiny:paste_as_text'] = 'Paste as text'; $string['tiny:paste_column_after'] = 'Paste column after'; $string['tiny:paste_column_before'] = 'Paste column before'; -$string['tiny:paste_is_now_in_plain_text_mode._contents_will_now_be_pasted_as_plain_text_until_you_toggle_this_option_off.'] = 'Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.'; +$string['tiny:paste_is_now_in_plain_text_mode._contents_will_now_be_pasted_as_plain_text_until_you_'] = 'Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.'; $string['tiny:paste_or_type_a_link'] = 'Paste or type a link'; $string['tiny:paste_row_after'] = 'Paste row after'; $string['tiny:paste_row_before'] = 'Paste row before'; @@ -308,7 +308,7 @@ $string['tiny:resize'] = 'Resize'; $string['tiny:restore_last_draft'] = 'Restore last draft'; $string['tiny:rich_text_area'] = 'Rich Text Area'; $string['tiny:rich_text_area._press_alt-0_for_help.'] = 'Rich Text Area. Press ALT-0 for help.'; -$string['tiny:rich_text_area._press_alt-f9_for_menu._press_alt-f10_for_toolbar._press_alt-0_for_help'] = 'Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help'; +$string['tiny:rich_text_area._press_alt-f9_for_menu._press_alt-f10_for_toolbar._press_alt-0_for_hel'] = 'Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help'; $string['tiny:ridge'] = 'Ridge'; $string['tiny:right'] = 'Right'; $string['tiny:right_to_left'] = 'Right to left'; @@ -355,9 +355,9 @@ $string['tiny:templates'] = 'Templates'; $string['tiny:text'] = 'Text'; $string['tiny:text_color'] = 'Text color'; $string['tiny:text_to_display'] = 'Text to display'; -$string['tiny:the_url_you_entered_seems_to_be_an_email_address._do_you_want_to_add_the_required_mailto_prefix'] = 'The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?'; -$string['tiny:the_url_you_entered_seems_to_be_an_external_link._do_you_want_to_add_the_required_http_prefix'] = 'The URL you entered seems to be an external link. Do you want to add the required http:// prefix?'; -$string['tiny:the_url_you_entered_seems_to_be_an_external_link._do_you_want_to_add_the_required_https_prefix'] = 'The URL you entered seems to be an external link. Do you want to add the required https:// prefix?'; +$string['tiny:the_url_you_entered_seems_to_be_an_email_address._do_you_want_to_add_the_required_mai'] = 'The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?'; +$string['tiny:the_url_you_entered_seems_to_be_an_external_link._do_you_want_to_add_the_required_htt'] = 'The URL you entered seems to be an external link. Do you want to add the required http:// prefix?'; +$string['tiny:the_url_you_entered_seems_to_be_an_external_link._do_you_want_to_add_the_required_ht1'] = 'The URL you entered seems to be an external link. Do you want to add the required https:// prefix?'; $string['tiny:title'] = 'Title'; $string['tiny:to_open_the_popup_press_shiftenter'] = 'To open the popup, press Shift+Enter'; $string['tiny:tools'] = 'Tools'; @@ -388,7 +388,7 @@ $string['tiny:yellow'] = 'Yellow'; $string['tiny:yes'] = 'Yes'; $string['tiny:you_are_using_0'] = 'You are using {0}'; $string['tiny:you_have_unsaved_changes_are_you_sure_you_want_to_navigate_away'] = 'You have unsaved changes are you sure you want to navigate away?'; -$string['tiny:your_browser_doesnt_support_direct_access_to_the_clipboard._please_use_the_ctrlxcv_keyboard_shortcuts_instead.'] = 'Your browser doesn\'t support direct access to the clipboard. Please use the Ctrl+X/C/V keyboard shortcuts instead.'; +$string['tiny:your_browser_doesnt_support_direct_access_to_the_clipboard._please_use_the_ctrlxcv_ke'] = 'Your browser doesn\'t support direct access to the clipboard. Please use the Ctrl+X/C/V keyboard shortcuts instead.'; $string['tiny:alignment1'] = 'alignment'; $string['tiny:austral_sign'] = 'austral sign'; $string['tiny:cedi_sign'] = 'cedi sign'; diff --git a/lib/editor/tiny/tinystrings.json b/lib/editor/tiny/tinystrings.json index 88b54fac2f5..c0ca466ac6f 100644 --- a/lib/editor/tiny/tinystrings.json +++ b/lib/editor/tiny/tinystrings.json @@ -154,7 +154,7 @@ "tiny:horizontal_line": "Horizontal line", "tiny:horizontal_space": "Horizontal space", "tiny:id": "ID", - "tiny:id_should_start_with_a_letter_followed_only_by_letters_numbers_dashes_dots_colons_or_underscores.": "ID should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.", + "tiny:id_should_start_with_a_letter_followed_only_by_letters_numbers_dashes_dots_colons_or_": "ID should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.", "tiny:image_is_decorative": "Image is decorative", "tiny:image_list": "Image list", "tiny:image_title": "Image title", @@ -245,7 +245,7 @@ "tiny:paste_as_text": "Paste as text", "tiny:paste_column_after": "Paste column after", "tiny:paste_column_before": "Paste column before", - "tiny:paste_is_now_in_plain_text_mode._contents_will_now_be_pasted_as_plain_text_until_you_toggle_this_option_off.": "Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.", + "tiny:paste_is_now_in_plain_text_mode._contents_will_now_be_pasted_as_plain_text_until_you_": "Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.", "tiny:paste_or_type_a_link": "Paste or type a link", "tiny:paste_row_after": "Paste row after", "tiny:paste_row_before": "Paste row before", @@ -279,7 +279,7 @@ "tiny:restore_last_draft": "Restore last draft", "tiny:rich_text_area": "Rich Text Area", "tiny:rich_text_area._press_alt-0_for_help.": "Rich Text Area. Press ALT-0 for help.", - "tiny:rich_text_area._press_alt-f9_for_menu._press_alt-f10_for_toolbar._press_alt-0_for_help": "Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help", + "tiny:rich_text_area._press_alt-f9_for_menu._press_alt-f10_for_toolbar._press_alt-0_for_hel": "Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help", "tiny:ridge": "Ridge", "tiny:right": "Right", "tiny:right_to_left": "Right to left", @@ -326,9 +326,9 @@ "tiny:text": "Text", "tiny:text_color": "Text color", "tiny:text_to_display": "Text to display", - "tiny:the_url_you_entered_seems_to_be_an_email_address._do_you_want_to_add_the_required_mailto_prefix": "The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?", - "tiny:the_url_you_entered_seems_to_be_an_external_link._do_you_want_to_add_the_required_http_prefix": "The URL you entered seems to be an external link. Do you want to add the required http:// prefix?", - "tiny:the_url_you_entered_seems_to_be_an_external_link._do_you_want_to_add_the_required_https_prefix": "The URL you entered seems to be an external link. Do you want to add the required https:// prefix?", + "tiny:the_url_you_entered_seems_to_be_an_email_address._do_you_want_to_add_the_required_mai": "The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?", + "tiny:the_url_you_entered_seems_to_be_an_external_link._do_you_want_to_add_the_required_htt": "The URL you entered seems to be an external link. Do you want to add the required http:// prefix?", + "tiny:the_url_you_entered_seems_to_be_an_external_link._do_you_want_to_add_the_required_ht1": "The URL you entered seems to be an external link. Do you want to add the required https:// prefix?", "tiny:title": "Title", "tiny:to_open_the_popup_press_shiftenter": "To open the popup, press Shift+Enter", "tiny:tools": "Tools", @@ -359,7 +359,7 @@ "tiny:yes": "Yes", "tiny:you_are_using_0": "You are using {0}", "tiny:you_have_unsaved_changes_are_you_sure_you_want_to_navigate_away": "You have unsaved changes are you sure you want to navigate away?", - "tiny:your_browser_doesnt_support_direct_access_to_the_clipboard._please_use_the_ctrlxcv_keyboard_shortcuts_instead.": "Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X/C/V keyboard shortcuts instead.", + "tiny:your_browser_doesnt_support_direct_access_to_the_clipboard._please_use_the_ctrlxcv_ke": "Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X/C/V keyboard shortcuts instead.", "tiny:alignment1": "alignment", "tiny:austral_sign": "austral sign", "tiny:cedi_sign": "cedi sign", diff --git a/lib/editor/tiny/tools/getOriginals.mjs b/lib/editor/tiny/tools/getOriginals.mjs index e2a028b5a8c..1b2514f5e87 100644 --- a/lib/editor/tiny/tools/getOriginals.mjs +++ b/lib/editor/tiny/tools/getOriginals.mjs @@ -15,6 +15,8 @@ import {readFile, writeFile} from 'fs/promises'; +const maxStringIdentifierLength = 90; + const readStringsFromLanguages = async (language) => { const fileContent = await readFile(`./langs/${language}.js`, 'utf-8'); @@ -47,6 +49,11 @@ const getStringMap = (strings) => { stringKey = `tiny:${stringKey}`; + if (stringKey.length > maxStringIdentifierLength) { + const modifierLength = modifier === 0 ? 0 : `${modifier}`.length; + stringKey = stringKey.slice(0, maxStringIdentifierLength - modifierLength); + } + if (modifier > 0) { stringKey = `${stringKey}${modifier}`; } From 6ee0d6a979d92ef9dcf9b6714d15a1258b828996 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Wed, 16 Nov 2022 21:40:46 +0800 Subject: [PATCH 2/2] MDL-76331 editor_tiny: Generate all langstrings This change renames the generator and has it generate the language strings for all available languages for easier import into AMOS. --- lib/editor/tiny/readme_moodle.md | 17 +++-- ...getOriginals.mjs => createLangStrings.mjs} | 76 +++++++++++++++---- 2 files changed, 71 insertions(+), 22 deletions(-) rename lib/editor/tiny/tools/{getOriginals.mjs => createLangStrings.mjs} (52%) diff --git a/lib/editor/tiny/readme_moodle.md b/lib/editor/tiny/readme_moodle.md index 07dff6205f5..72982c89cc5 100644 --- a/lib/editor/tiny/readme_moodle.md +++ b/lib/editor/tiny/readme_moodle.md @@ -43,23 +43,23 @@ ## Update procedure for included TinyMCE translations -1. Visit https://www.tiny.cloud/get-tiny/language-packages/ and download a translation which has been fully translated, for example the German translation. -2. If you did not download the German translation, update the final line of `tools/getOriginals.mjs` to the language code for the relevant translation. +1. Visit https://www.tiny.cloud/get-tiny/language-packages/ and download the "TinyMCE 6 All languages" zip file. +2. Check the list of languages and confirm that the German translation is still at 100%. If not, then make a note of a language which is. 3. Unzip the translation into a new directory: - ``` + ```bash langdir=`mktemp -d` cd "${langdir}" - unzip path/to/de.zip + unzip path/to/langs.zip ``` 4. Run the translation tool: - ``` - node "${MOODLEDIR}/tools/getOriginals.mjs" + ```bash + node "${MOODLEDIR}/tools/createLangStrings.mjs" ``` - This will generate two files + This will generate a language file for each available Language, as well as a `tinystrings.json`, and a `strings.php` which will be used in the subsequent steps. 5. Copy the `tinystrings.json` file into the Moodle directory @@ -75,9 +75,12 @@ ``` 7. Commit changes +8. If required, the remaining language strings can be fed into AMOS. --- +**Note:** A set of language files are also generated for all supported translations and may be submitted to AMOS if desired. + **Note:** You will need to manually check for any Moodle-updated language strings as part of this change (for example any from the en_fixes). --- diff --git a/lib/editor/tiny/tools/getOriginals.mjs b/lib/editor/tiny/tools/createLangStrings.mjs similarity index 52% rename from lib/editor/tiny/tools/getOriginals.mjs rename to lib/editor/tiny/tools/createLangStrings.mjs index 1b2514f5e87..d0f747dd077 100644 --- a/lib/editor/tiny/tools/getOriginals.mjs +++ b/lib/editor/tiny/tools/createLangStrings.mjs @@ -13,23 +13,28 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -import {readFile, writeFile} from 'fs/promises'; +import {readdir, readFile, writeFile, mkdir} from 'fs/promises'; +import {join as joinPath} from 'path'; +const fullyTranslatedLanguage = 'de'; const maxStringIdentifierLength = 90; const readStringsFromLanguages = async (language) => { const fileContent = await readFile(`./langs/${language}.js`, 'utf-8'); - const translations = []; + let translations = {}; const tinymce = { addI18n: (language, strings) => { - translations.push(...(Object.keys(strings))); + translations = strings; }, }; eval(fileContent); - return translations.sort(); + return Object.keys(translations).sort().reduce((sortedTranslations, key) => { + sortedTranslations[key] = translations[key]; + return sortedTranslations; + }, {}); }; const getStringMap = (strings) => { @@ -41,7 +46,7 @@ const getStringMap = (strings) => { .replaceAll(/\{(\d)\}/g, '$1') .replaceAll('#', 'hash') .replaceAll(/[^a-z0-9_\-\.]/g, '') - ; + ; if (stringKey === '') { throw new Error(`The calculated key for '${string}' was empty`); @@ -77,17 +82,58 @@ const getStringMap = (strings) => { return stringMap; }; -const getPhpStrings = (stringMap) => Object.entries(stringMap).map(([stringKey, stringValue]) => { - return `$string['${stringKey}'] = '${stringValue.replace("'", "\\\'")}';` -}).join("\n"); +const getPhpStrings = (stringMap, translatedStrings) => Object.entries(stringMap).map(([stringKey, englishString]) => { + if (translatedStrings[englishString].length === 0) { + return null; + } + return `$string['${stringKey}'] = '${translatedStrings[englishString].replaceAll("'", "\\\'")}';` +}) +.filter((value) => value !== null) +.join("\n"); - -const constructTranslationFile = async(language) => { - const strings = await readStringsFromLanguages(language); - const stringMap = getStringMap(strings); - - await writeFile('./strings.php', getPhpStrings(stringMap) + "\n"); +const storeEnglishStrings = async(stringMap) => { + const englishStrings = Object.entries(stringMap).map(([stringKey, stringValue]) => { + return `$string['${stringKey}'] = '${stringValue.replace("'", "\\\'")}';` + }).join("\n"); + await writeFile('./strings.php', englishStrings + "\n"); await writeFile('./tinystrings.json', JSON.stringify(stringMap, null, ' ')); +} + +const constructTranslationFile = async(language, englishStringMap = null) => { + const strings = await readStringsFromLanguages(language); + console.log(`Generating translation data for ${language} with ${Object.keys(strings).length} strings`); + const stringMap = englishStringMap === null ? getStringMap(Object.keys(strings)) : englishStringMap; + + const langDir = joinPath('lang', language); + await mkdir(langDir, {recursive: true}); + + const fileContent = ` { + const {stringMap} = await constructTranslationFile(fullyTranslatedLanguage); + storeEnglishStrings(stringMap); + + readdir('./langs/').then((files) => { + files.forEach(async(file) => { + const langIdent = file.replace('.js', ''); + if (langIdent === fullyTranslatedLanguage) { + // This language is already done. + return; + } + await constructTranslationFile(langIdent, stringMap); + }); + }); +} + +constructTranslationFiles();