From 2dee11ec95acb9d5dd5553d630357c4ad331c630 Mon Sep 17 00:00:00 2001 From: christian Date: Fri, 17 Nov 2023 17:28:54 -0500 Subject: [PATCH] Language: Add sub-languages using gettext for symfony and vue - refs BT#20922 --- public/main/admin/sub_language.php | 352 +++--------- public/main/admin/sub_language_add.php | 189 +------ public/main/admin/sub_language_ajax.inc.php | 88 +-- .../main/inc/lib/internationalization.lib.php | 28 +- public/main/inc/lib/sub_language.class.php | 507 ++++++++++++++---- .../Command/UpdateVueTranslations.php | 35 +- 6 files changed, 582 insertions(+), 617 deletions(-) diff --git a/public/main/admin/sub_language.php b/public/main/admin/sub_language.php index 44d86d7ac0..035ece69e0 100644 --- a/public/main/admin/sub_language.php +++ b/public/main/admin/sub_language.php @@ -1,8 +1,6 @@ $(function () { - $(".save").click(function() { - var button_name=$(this).attr("name"); - var button_array=button_name.split("|"); - var button_name=button_array[1]; - var file_id=button_array[2]; - var is_variable_language="$"+button_name; - - var is_new_language = $("#txtid_"+file_id+"_"+button_name).val(); - if (is_new_language == undefined) { - is_new_language="_"; - } - if (is_new_language.length>0 && is_new_language!="_" && file_id!="" && button_name!="") { - $.ajax({ - contentType: "application/x-www-form-urlencoded", - beforeSend: function(myObject) { - $("#div_message_information_id").html("
"); - }, - type: "POST", - url: "../admin/sub_language_ajax.inc.php", - data: { - \'new_language\': is_new_language, - \'variable_language\': is_variable_language, - \'file_id\': file_id, - \'id\': '.intval($_REQUEST['id']).', - \'sub\': '.intval($_REQUEST['sub_language_id']).', - \'sub_language_id\': '.intval($_REQUEST['sub_language_id']).' + $(".save").on("click", function() { + var $this = $(this); + var buttonId = $this.attr("id"); + var textareaId = "txtid_" + buttonId.split("_")[1] + "_" + buttonId.split("_")[2]; + var content = $("#" + textareaId).val(); + var filename = $this.data("filename"); + var msgidEncoded = $this.data("msgid"); + + $this.prop("disabled", true).text("'.get_lang('Loading').'..."); + + try { + $.post( + "' . api_get_path(WEB_CODE_PATH) . 'admin/sub_language_ajax.inc.php", + { + content: content, + filename: filename, + msgidEncoded: msgidEncoded }, - success: function(datos) { - if (datos == "1") { - $("#div_message_information_id").html(\''.Display::return_message(get_lang('The new word has been added'), 'success').'\'); + function(data) { + if (data.success) { + localStorage.setItem("redirectTo", textareaId); + window.location.reload(true); } else { - $("#div_message_information_id").html("
" + datos +"
"); + alert("Error: " + (data.error || "'.get_lang('Error, try it again').'")); + $this.prop("disabled", false).text("'.get_lang('Save').'"); } - } - }); - } else { - $("#div_message_information_id").html(\''.Display::return_message(get_lang('The form contains incorrect or incomplete data. Please check your input.'), 'error').'\'); + }, + "json" + ); + } catch (e) { + console.error("Error to check JSON:", e); + $this.prop("disabled", false).text("'.get_lang('Save').'"); } }); }); @@ -72,11 +65,6 @@ if (isset($_GET['id']) && $_GET['id'] == strval(intval($_GET['id']))) { $sub_language_name = SubLanguageManager::get_name_of_language_by_id($_GET['sub_language_id']); $all_data_of_language = SubLanguageManager::get_all_information_of_language($_GET['id']); $all_data_of_sublanguage = SubLanguageManager::get_all_information_of_language($_GET['sub_language_id']); - $sub_language_file = api_get_path(SYS_LANG_PATH).$all_data_of_sublanguage['dokeos_folder']; - - if (!file_exists($sub_language_file) || !is_writable($sub_language_file)) { - $sublanguage_folder_error = $sub_language_file.' '.get_lang('is not writeable'); - } if (true === SubLanguageManager::check_if_exist_language_by_id($_GET['id'])) { $language_id_exist = true; } else { @@ -88,11 +76,6 @@ if (isset($_GET['id']) && $_GET['id'] == strval(intval($_GET['id']))) { } $intro = sprintf(get_lang('Define new terms for sub-language %s by searching some term, then save each translation by clicking the save button. You will then have to switch your own user language to see the new terms appear.'), strtolower($sub_language_name)); -$path_folder = api_get_path(SYS_LANG_PATH).$all_data_of_language['dokeos_folder']; - -if (!is_dir($path_folder) || 0 == strlen($all_data_of_language['dokeos_folder'])) { - api_not_allowed(true); -} Display :: display_header($language_name); @@ -121,237 +104,61 @@ if (!empty($sublanguage_folder_error)) { echo '
 
'; /** - * @param $term The term to search - * @param bool $search_in_variable The search will include the variable definition of the term - * @param bool $search_in_english The search will include the english language variables - * @param bool $search_in_parent The search will include the parent language variables of the sub language - * @param bool $search_in_sub_language The search will include the sub language variables + * Searches for a term in language files and returns results. + * + * @param string $term The term to search for. + * @param int $subLanguageId The ID of the sub-language to search in. * * @author Julio Montoya * - * @return array + * @return array An array containing search results. */ -function search_language_term( - $term, - $search_in_variable = true, - $search_in_english = true, - $search_in_parent = true, - $search_in_sub_language = true -) { - //These the $_REQUEST['id'] and the $_REQUEST['sub_language_id'] variables are process in global.inc.php (LOAD LANGUAGE FILES SECTION) - /* - These 4 arrays are set in global.inc.php with the condition that will be load from sub_language.php or sub_language_ajax.inc.php - $english_language_array - $parent_language_array - $sub_language_array - $language_files_to_load - */ - global $language_files_to_load, $sub_language_array, $english_language_array, $parent_language_array; - $language_files_to_load_keys = array_flip($language_files_to_load); - $array_to_search = $parent_language_array; - $list_info = []; - $term = '/'.Security::remove_XSS(trim($_REQUEST['txt_search_word'])).'/i'; - //@todo optimize this foreach - foreach ($language_files_to_load as $lang_file) { - //searching in parent language of the sub language - if ($search_in_parent) { - $variables = $parent_language_array[$lang_file]; - foreach ($variables as $parent_name_variable => $parent_variable_value) { - //arrays are avoided - if (is_array($parent_variable_value)) { - continue; - } - $founded = false; - // searching the item in the parent tool - if (0 !== preg_match($term, $parent_variable_value)) { - $founded = true; - } - if ($founded) { - //loading variable from the english array - $sub_language_name_variable = isset($sub_language_array[$lang_file][$parent_name_variable]) - ? $sub_language_array[$lang_file][$parent_name_variable] - : ''; - //loading variable from the english array - $english_name_variable = $english_language_array[$lang_file][$parent_name_variable]; - - //config buttons - /*if (strlen($english_name_variable)>1500) { - $size =20; - } else { - $size =4; - }*/ - - $obj_text = Display::tag( - 'textarea', - $sub_language_name_variable, - [ - 'rows' => 10, - 'cols' => 40, - 'name' => 'txt|'.$parent_name_variable.'|'.$language_files_to_load_keys[$lang_file], - 'id' => 'txtid_'.$language_files_to_load_keys[$lang_file].'_'.$parent_name_variable, - ] - ); - $obj_button = Display::button( - 'btn|'.$parent_name_variable.'|'.$language_files_to_load_keys[$lang_file], - get_lang('Save'), - [ - 'class' => 'save btn btn--plain btn-sm', - 'type' => 'button', - 'id' => 'btnid_'.$parent_name_variable, - ] - ); - - $list_info[$parent_name_variable] = [ - $lang_file.'.inc.php', - $parent_name_variable, - htmlentities($english_name_variable), - htmlentities($parent_variable_value), - $obj_text, - $obj_button, - ]; - } - } - } - - //search in english - if ($search_in_english || $search_in_variable) { - $variables = $english_language_array[$lang_file]; - foreach ($variables as $name_variable => $variable_value) { - if (isset($list_info[$name_variable])) { - continue; - } - - if (is_array($variable_value)) { - continue; - } - - if (is_array($variable_value)) { - echo $lang_file; - } - $founded = false; - if ($search_in_english && $search_in_variable) { - // searching the item in the parent tool - if (0 !== preg_match($term, $variable_value) || 0 !== preg_match($term, $name_variable)) { - $founded = true; - } - } else { - if ($search_in_english) { - if (0 !== preg_match($term, $variable_value)) { - $founded = true; - } - } else { - if (0 !== preg_match($term, $name_variable)) { - $founded = true; - } - } - } - - if ($founded) { - //loading variable from the english array - $sub_language_name_variable = null; - if (isset($sub_language_array[$lang_file][$name_variable])) { - $sub_language_name_variable = $sub_language_array[$lang_file][$name_variable]; - } - $parent_variable_value = null; - if (isset($parent_language_array[$lang_file][$name_variable])) { - $parent_variable_value = $parent_language_array[$lang_file][$name_variable]; - } - //config buttons - $obj_text = Display::tag( - 'textarea', - $sub_language_name_variable, - [ - 'rows' => 10, - 'cols' => 40, - 'name' => 'txt|'.$name_variable.'|'.$language_files_to_load_keys[$lang_file], - 'id' => 'txtid_'.$language_files_to_load_keys[$lang_file].'_'.$name_variable, - ] - ); - $obj_button = Display::button( - 'btn|'.$name_variable.'|'.$language_files_to_load_keys[$lang_file], - get_lang('Save'), - [ - 'class' => 'save btn btn--plain btn-sm', - 'type' => 'button', - 'id' => 'btnid_'.$name_variable, - ] - ); - - //loading variable from the english array - $english_name_variable = $english_language_array[$lang_file][$name_variable]; - - $list_info[] = [ - $lang_file.'.inc.php', - $name_variable, - htmlentities($english_name_variable), - htmlentities($parent_variable_value), - $obj_text, - $obj_button, - ]; - } - } - } - - // Search in sub language - if ($search_in_sub_language) { - $variables = $sub_language_array[$lang_file]; - foreach ($variables as $name_variable => $variable_value) { - if (is_array($parent_variable_value)) { - continue; - } - - if (is_array($variable_value)) { - continue; - } - - $founded = false; - // searching the item in the parent tool - if (0 !== preg_match($term, $variable_value)) { - $founded = true; - } - if ($founded) { - //loading variable from the english array - $sub_language_name_variable = isset($sub_language_array[$lang_file][$name_variable]) - ? $sub_language_array[$lang_file][$name_variable] - : ''; - $parent_variable_value = isset($parent_language_array[$lang_file][$name_variable]) - ? $parent_language_array[$lang_file][$name_variable] - : ''; - //config buttons - $obj_text = Display::tag( - 'textarea', - $sub_language_name_variable, - [ - 'rows' => 10, - 'cols' => 40, - 'name' => 'txt|'.$name_variable.'|'.$language_files_to_load_keys[$lang_file], - 'id' => 'txtid_'.$language_files_to_load_keys[$lang_file].'_'.$name_variable, - ] - ); - $obj_button = Display::button( - 'btn|'.$name_variable.'|'.$language_files_to_load_keys[$lang_file], - get_lang('Save'), - [ - 'class' => 'save btn btn--plain btn-sm', - 'type' => 'button', - 'id' => 'btnid_'.$name_variable, - ] - ); - - //loading variable from the english array - $english_name_variable = $english_language_array[$lang_file][$name_variable]; - $list_info[] = [$lang_file.'.inc.php', - $name_variable, - $english_name_variable, - $parent_variable_value, $obj_text, $obj_button, ]; - } - } +function search_language_term($term, $subLanguageId) +{ + $translations = SubLanguageManager::searchTranslations($term, $subLanguageId); + $listInfo = []; + if (!empty($translations)) { + $i = 0; + foreach ($translations as $trans) { + $keys = array_keys($trans); + $firstTranslationKey = $keys[4]; + $secondTranslationKey = $keys[5]; + $langFile = "messages.$secondTranslationKey.po"; + $objText = Display::tag( + 'textarea', + $trans[$secondTranslationKey], + [ + 'rows' => 10, + 'cols' => 40, + 'name' => 'txt|'.$trans['phpVarName'].'|'.$i, + 'id' => 'txtid_'.$i.'_'.$trans['phpVarName'], + ] + ); + $objButton = Display::button( + 'btn|'.$trans['phpVarName'].'|'.$i, + get_lang('Save'), + [ + 'class' => 'save btn btn--plain btn-sm', + 'type' => 'button', + 'id' => 'btnid_'.$i.'_'.$trans['phpVarName'], + 'data-filename' => $langFile, + 'data-msgid' => base64_encode($trans['variable']), + ] + ); + $listInfo[$i] = [ + $langFile, + $trans['variable'], + htmlentities($trans['en']), + htmlentities($trans[$firstTranslationKey]), + $objText, + $objButton, + ]; + + $i++; } } - $list_info = array_unique_dimensional($list_info); - - return $list_info; + return $listInfo; } // Allow see data in sort table @@ -361,10 +168,7 @@ if (isset($_REQUEST['txt_search_word'])) { if (strlen(trim($_REQUEST['txt_search_word'])) > 2) { $list_info = search_language_term( $_REQUEST['txt_search_word'], - true, - true, - true, - true + $_GET['sub_language_id'], ); } } diff --git a/public/main/admin/sub_language_add.php b/public/main/admin/sub_language_add.php index b261ff6194..d83078d9ba 100644 --- a/public/main/admin/sub_language_add.php +++ b/public/main/admin/sub_language_add.php @@ -20,149 +20,10 @@ $tool_name = get_lang('Create sub-language'); $interbreadcrumb[] = ['url' => 'index.php', 'name' => get_lang('Administration')]; $interbreadcrumb[] = ['url' => 'languages.php', 'name' => get_lang('Chamilo Portal Languages')]; -/** - * Add sub-language. - * - * @deprecated - * - * @param string Original language name (Occitan, Wallon, Vlaams) - * @param string English language name (occitan, wallon, flanders) - * @param string ISO code (fr_FR, ...) - * @param int Whether the sublanguage is published (0=unpublished, 1=published) - * @param int ID del idioma padre - * - * @return false|string New sub language ID or false on error - */ -function add_sub_language($original_name, $english_name, $isocode, $sublanguage_available, $parent_id) -{ - $tbl_admin_languages = Database::get_main_table(TABLE_MAIN_LANGUAGE); - $original_name = Database::escape_string($original_name); - $english_name = Database::escape_string($english_name); - $isocode = Database::escape_string($isocode); - $sublanguage_available = Database::escape_string($sublanguage_available); - $parent_id = intval($parent_id); - - $sql = 'INSERT INTO '.$tbl_admin_languages.'(original_name,english_name,isocode,dokeos_folder,available,parent_id) - VALUES ("'.$original_name.'","'.$english_name.'","'.$isocode.'","'.$english_name.'","'.$sublanguage_available.'","'.$parent_id.'")'; - $res = Database::query($sql); - if (false === $res) { - return false; - } - - return Database::insert_id(); -} - -/** - * Check if language exists. - * - * @param string Original language name (Occitan, Wallon, Vlaams) - * @param string English language name (occitan, wallon, flanders) - * @param string ISO code (fr_FR, ...) - * @param int Whether the sublanguage is published (0=unpublished, 1=published) - * - * @return array Array describing the number of items found that match the - * current language insert attempt (original_name => true, - * english_name => true, isocode => true, - * execute_add => true/false). If execute_add is true, then we - * can proceed. - * - * @todo This function is not transaction-safe and should probably be included - * inside the add_sub_language function. - */ -function check_if_language_exist($original_name, $english_name, $isocode, $sublanguage_available) -{ - $tbl_admin_languages = Database::get_main_table(TABLE_MAIN_LANGUAGE); - $sql_original_name = 'SELECT count(*) AS count_original_name FROM '.$tbl_admin_languages.' - WHERE original_name="'.Database::escape_string($original_name).'" '; - $sql_english_name = 'SELECT count(*) AS count_english_name FROM '.$tbl_admin_languages.' - WHERE english_name="'.Database::escape_string($english_name).'" '; - $rs_original_name = Database::query($sql_original_name); - $rs_english_name = Database::query($sql_english_name); - $count_original_name = Database::result($rs_original_name, 0, 'count_original_name'); - $count_english_name = Database::result($rs_english_name, 0, 'count_english_name'); - - $has_error = false; - $message_information = []; - - if (1 == $count_original_name) { - $has_error = true; - $message_information['original_name'] = true; - } - if (1 == $count_english_name) { - $has_error = true; - $message_information['english_name'] = true; - } - - $iso_list = api_get_platform_isocodes(); - $iso_list = array_values($iso_list); - - if (!in_array($isocode, $iso_list)) { - $has_error = true; - $message_information['isocode'] = true; - } - if (true === $has_error) { - $message_information['execute_add'] = false; - } - if (false === $has_error) { - $message_information['execute_add'] = true; - } - - return $message_information; -} - -/** - * Check if language exist, given its ID. This is just a wrapper for the - * SubLanguageManager::check_if_exist_language_by_id() method and should not exist. - * - * @param int Language ID - * - * @return bool - * - * @todo deprecate this function and use the static method directly - */ -function check_if_exist_language_by_id($language_id) -{ - return SubLanguageManager::check_if_exist_language_by_id($language_id); -} - -/** - * Check if the given language is a parent of any sub-language. - * - * @param int Language ID of the presumed parent - * - * @return bool True if this language has children, false otherwise - */ -function ckeck_if_is_parent_of_sub_language($parent_id) -{ - $sql = 'SELECT count(*) AS count FROM language WHERE parent_id= '.intval($parent_id); - $rs = Database::query($sql); - if (Database::num_rows($rs) > 0 && 1 == Database::result($rs, 0, 'count')) { - return true; - } else { - return false; - } -} - -/** - * Get all information of sub-language. - * - * @param int Parent language ID - * @param int Child language ID - * - * @return array - */ -function allow_get_all_information_of_sub_language($parent_id, $sub_language_id) -{ - return SubLanguageManager::get_all_information_of_sub_language($parent_id, $sub_language_id); -} - -/*end declare functions*/ - //add data - if (isset($_GET['sub_language_id']) && $_GET['sub_language_id'] == strval(intval($_GET['sub_language_id']))) { $language_name = SubLanguageManager::get_name_of_language_by_id($_GET['sub_language_id']); - if (true === check_if_exist_language_by_id($_GET['sub_language_id'])) { + if (true === SubLanguageManager::languageExistsById($_GET['sub_language_id'])) { $sub_language_id = $_GET['sub_language_id']; $sub_language_id_exist = true; } else { @@ -174,8 +35,8 @@ $language_name = ''; if (isset($_GET['id']) && $_GET['id'] == strval(intval($_GET['id']))) { $language_details = SubLanguageManager::get_all_information_of_language($_GET['id']); $language_name = $language_details['original_name']; - if (true === check_if_exist_language_by_id($_GET['id'])) { - $parent_id = $_GET['id']; + if (true === SubLanguageManager::languageExistsById($_GET['id'])) { + $parent_id = (int) $_GET['id']; $language_id_exist = true; } else { $language_id_exist = false; @@ -189,8 +50,8 @@ if (isset($_GET['id']) && $_GET['id'] == strval(intval($_GET['id']))) { if ((isset($_GET['id']) && $_GET['id'] == strval(intval($_GET['id']))) && (isset($_GET['sub_language_id']) && $_GET['sub_language_id'] == strval(intval($_GET['sub_language_id']))) ) { - if (true === check_if_exist_language_by_id($_GET['id']) && true === check_if_exist_language_by_id($_GET['sub_language_id'])) { - $get_all_information = allow_get_all_information_of_sub_language($_GET['id'], $_GET['sub_language_id']); + if (true === SubLanguageManager::languageExistsById($_GET['id']) && true === SubLanguageManager::languageExistsById($_GET['sub_language_id'])) { + $get_all_information = SubLanguageManager::getAllInformationOfSubLanguage((int) $_GET['id'], (int) $_GET['sub_language_id']); $original_name = $get_all_information['original_name']; $english_name = $get_all_information['english_name']; $isocode = $get_all_information['isocode']; @@ -199,7 +60,7 @@ if ((isset($_GET['id']) && $_GET['id'] == strval(intval($_GET['id']))) && $language_name = get_lang('Create sub-languageForLanguage').' ( '.strtolower($language_name).' )'; -if (true === ckeck_if_is_parent_of_sub_language($parent_id) && +if (true === SubLanguageManager::isParentOfSubLanguage($parent_id) && isset($_GET['action']) && 'deletesublanguage' == $_GET['action'] ) { $language_name = get_lang('Delete sub-language'); @@ -214,9 +75,9 @@ if (isset($_POST['SubmitAddNewLanguage'])) { $english_name = str_replace(' ', '_', $english_name); $isocode = str_replace(' ', '_', $isocode); - $sublanguage_available = $_POST['sub_language_is_visible']; + $sublanguage_available = isset($_POST['sub_language_is_visible']) ? (int) $_POST['sub_language_is_visible'] : 0; $check_information = []; - $check_information = check_if_language_exist($original_name, $english_name, $isocode, $sublanguage_available); + $check_information = SubLanguageManager::checkIfLanguageExists($original_name, $english_name, $isocode); foreach ($check_information as $index_information => $value_information) { $allow_insert_info = false; if ('original_name' == $index_information) { @@ -248,20 +109,17 @@ if (isset($_POST['SubmitAddNewLanguage'])) { $isocode = str_replace(' ', '_', $isocode); $str_info = '
'.get_lang('Original name').' : '.$original_name.'
'.get_lang('English name').' : '.$english_name.'
'.get_lang('Character set').' : '.$isocode; - $mkdir_result = SubLanguageManager::add_language_directory($english_name); + $mkdir_result = SubLanguageManager::addPoFileForSubLanguage($english_name); if ($mkdir_result) { - $sl_id = add_sub_language($original_name, $english_name, $isocode, $sublanguage_available, $parent_id); + $sl_id = SubLanguageManager::addSubLanguage($original_name, $english_name, $sublanguage_available, $parent_id); if (false === $sl_id) { - SubLanguageManager::remove_language_directory($english_name); + SubLanguageManager::removePoFileForSubLanguage($english_name); $msg .= Display::return_message(get_lang('The /main/lang directory, used on this portal to store the languages, is not writable. Please contact your platform administrator and report this message.'), 'error'); } else { Display::addFlash( Display::return_message(get_lang('The new sub-language has been added').$str_info, null, false) ); - unset($interbreadcrumb); - $_GET['sub_language_id'] = $_REQUEST['sub_language_id'] = $sl_id; - require 'sub_language.php'; - exit(); + api_location(api_get_path(WEB_CODE_PATH).'admin/languages.php?sub_language_id='.$sl_id); } } else { $msg .= Display::return_message(get_lang('The /main/lang directory, used on this portal to store the languages, is not writable. Please contact your platform administrator and report this message.'), 'error'); @@ -276,19 +134,24 @@ if (isset($_POST['SubmitAddNewLanguage'])) { } } +if (isset($_POST['SubmitAddDeleteLanguage'])) { + $removed = SubLanguageManager::removeSubLanguage($_GET['id'], $_GET['sub_language_id']); + if ($removed) { + Display::addFlash( + Display::return_message( + get_lang( + 'The sub language has been removed.' + ) + ) + ); + api_location(api_get_path(WEB_CODE_PATH).'admin/languages.php'); + } +} + Display:: display_header($language_name); echo $msg; -if (isset($_POST['SubmitAddDeleteLanguage'])) { - $rs = SubLanguageManager::remove_sub_language($_GET['id'], $_GET['sub_language_id']); - if (true === $rs) { - echo Display::return_message(get_lang('The sub language has been removed'), 'confirm'); - } else { - echo Display::return_message(get_lang('The sub-language has not been removed.'), 'error'); - } -} -// ckeck_if_is_parent_of_sub_language($parent_id)===false if (isset($_GET['action']) && 'definenewsublanguage' == $_GET['action']) { $text = $language_name; $form = new FormValidator( diff --git a/public/main/admin/sub_language_ajax.inc.php b/public/main/admin/sub_language_ajax.inc.php index 35c42c8a29..e55f4a85ce 100644 --- a/public/main/admin/sub_language_ajax.inc.php +++ b/public/main/admin/sub_language_ajax.inc.php @@ -2,90 +2,20 @@ /* For licensing terms, see /license.txt */ -exit; - -use Chamilo\CoreBundle\Entity\ExtraField; - /** * Sub language AJAX script to update variables. */ -$this_script = 'sub_language'; -require_once __DIR__.'/../inc/global.inc.php'; +require_once __DIR__ . '/../inc/global.inc.php'; api_protect_admin_script(); -$new_language = Security::remove_XSS($_REQUEST['new_language']); -$language_variable = Security::remove_XSS($_REQUEST['variable_language']); -$file_id = intval($_REQUEST['file_id']); - -if (isset($new_language) && isset($language_variable) && isset($file_id)) { - $file_language = $language_files_to_load[$file_id].'.inc.php'; - $id_language = intval($_REQUEST['id']); - $sub_language_id = intval($_REQUEST['sub']); - $all_data_of_language = SubLanguageManager::get_all_information_of_sub_language($id_language, $sub_language_id); - - $path_folder = api_get_path(SYS_LANG_PATH).$all_data_of_language['dokeos_folder'].'/'.$file_language; - $all_file_of_directory = SubLanguageManager::get_all_language_variable_in_file($path_folder); - $return_value = SubLanguageManager::add_file_in_language_directory($path_folder); - - //update variable language - // Replace double quotes to avoid parse errors - $new_language = str_replace('"', '\"', $new_language); - // Replace new line signs to avoid parse errors - see #6773 - $new_language = str_replace("\n", "\\n", $new_language); - $all_file_of_directory[$language_variable] = "\"".$new_language."\";"; - $result_array = []; - - foreach ($all_file_of_directory as $key_value => $value_info) { - $result_array[$key_value] = SubLanguageManager::write_data_in_file($path_folder, $value_info, $key_value); - } - $variables_with_problems = ''; - if (!empty($result_array)) { - foreach ($result_array as $key => $result) { - if (false == $result) { - $variables_with_problems .= $key.'
'; - } - } - } - - if (isset($_REQUEST['redirect'])) { - $message = Display::return_message(get_lang('The new word has been added'), 'success'); - - if (!empty($variables_with_problems)) { - Display::return_message( - $path_folder.' '.get_lang('is not writeable').'
'.api_ucwords(get_lang('errors found')) - .':
'.$variables_with_problems, - 'error' - ); - } - - Display::addFlash($message); - - if (isset($_REQUEST['item_type'])) { - $redirectUrl = api_get_path(WEB_CODE_PATH).'admin/extra_fields.php'; - - switch ($_REQUEST['item_type']) { - case ExtraField::USER_FIELD_TYPE: - header("Location: {$redirectUrl}?type=user"); - exit; - case ExtraField::COURSE_FIELD_TYPE: - header("Location: {$redirectUrl}?type=course"); - exit; - case ExtraField::SESSION_FIELD_TYPE: - header("Location: {$redirectUrl}?type=session"); - exit; - } - } - - if (isset($_REQUEST['skill'])) { - header('Location: '.api_get_path(WEB_CODE_PATH).'skills/skill_list.php'); - exit; - } - } +if (isset($_POST['content'], $_POST['filename'], $_POST['msgidEncoded'])) { + $content = $_POST['content']; + $filename = $_POST['filename']; + $msgid = base64_decode($_POST['msgidEncoded']); - if (!empty($variables_with_problems)) { - echo $path_folder.' '.get_lang('is not writeable').'
'.api_ucwords(get_lang('errors found')).':
'.$variables_with_problems; - } else { - echo 1; - } + $response = SubLanguageManager::updateOrAddMsgid($filename, $msgid, $content); + echo json_encode($response); +} else { + echo json_encode(["success" => false, "error" => get_lang('POST data missing')]); } diff --git a/public/main/inc/lib/internationalization.lib.php b/public/main/inc/lib/internationalization.lib.php index 06673b1a24..3c45b99e3d 100644 --- a/public/main/inc/lib/internationalization.lib.php +++ b/public/main/inc/lib/internationalization.lib.php @@ -66,6 +66,8 @@ function get_lang($variable) return $variable; } + $defaultLocale = 'en'; + $locale = api_get_language_isocode(); $userInfo = api_get_user_info(); if (is_array($userInfo) && !empty($userInfo['language'])) { @@ -79,12 +81,36 @@ function get_lang($variable) // Using symfony $defaultDomain = 'messages'; - return $translator->trans( + $translated = $translator->trans( $variable, [], $defaultDomain, $locale ); + + if ('true' === api_get_setting('language.allow_use_sub_language')) { + $parentLocale = null; + if ($translated === $variable && $locale !== $defaultLocale) { + $parentLocale = SubLanguageManager::getParentLocale($locale); + $translated = $translator->trans( + $variable, + [], + $defaultDomain, + $parentLocale + ); + } + + if ($translated === $variable && $parentLocale !== $defaultLocale) { + $translated = $translator->trans( + $variable, + [], + $defaultDomain, + $defaultLocale + ); + } + } + + return $translated; } /** diff --git a/public/main/inc/lib/sub_language.class.php b/public/main/inc/lib/sub_language.class.php index 29a4669ada..ab1334631f 100644 --- a/public/main/inc/lib/sub_language.class.php +++ b/public/main/inc/lib/sub_language.class.php @@ -1,11 +1,17 @@ 0) { - foreach ($content as $value_content) { - $path_file = $dir.'/'.$value_content; - unlink($path_file); + // If the .po file doesn't exist, create it + if (!file_exists($poFilePath)) { + $initialContent = "# Translation file for $subLanguageIsoCode\nmsgid \"\"\nmsgstr \"\"\n"; + if (false === file_put_contents($poFilePath, $initialContent)) { + return false; // Failed to write the file } - - return @rmdir($dir); - } else { - return @rmdir($dir); } + + return true; } /** @@ -552,4 +479,386 @@ class SubLanguageManager return false; } + + /** + * Convert a string to a valid PHP camelCase variable name. + * + * @param string $string + * @return string + */ + public static function stringToCamelCaseVariableName($string) + { + $varName = preg_replace('/[^a-z0-9_]/i', '_', $string); // Replace invalid characters with '_' + $varName = trim($varName, '_'); // Trim any '_' from the beginning and end + $varName = ucwords(str_replace('_', ' ', $varName)); // Convert to camel case + $varName = lcfirst(str_replace(' ', '', $varName)); // Remove spaces and convert the first character to lowercase + return substr($varName, 0, 25); // Limit to 15 characters + } + + /** + * Retrieve the iso_code for a given language ID and its parent. + * + * @param int $languageId + * @return array [childIsoCode, parentIsoCode] + */ + public static function getIsoCodes($languageId) + { + $em = Database::getManager(); + $language = $em->getRepository('Chamilo\CoreBundle\Entity\Language')->find($languageId); + + if (!$language) { + return [null, null]; + } + + $childIsoCode = $language->getIsoCode(); + $parentIsoCode = null; + + if ($language->getParent()) { + $parentLanguage = $em->getRepository('Chamilo\CoreBundle\Entity\Language')->find($language->getParent()); + if ($parentLanguage) { + $parentIsoCode = $parentLanguage->getIsoCode(); + } + } + + return [$childIsoCode, $parentIsoCode]; + } + + /** + * Search for translations based on a term and language ID. + * + * @param string $term The term to search for. + * @param int $languageId The ID of the language to search in. + * + * @return array An array of matched translations. + */ + public static function searchTranslations($term, $languageId): array + { + // Retrieve the ISO codes for the provided language ID. + list($childIsoCode, $parentIsoCode) = self::getIsoCodes($languageId); + + // Define the files to search in based on the ISO codes. + $files = ['en' => 'messages.en.po', $parentIsoCode => "messages.$parentIsoCode.po", $childIsoCode => "messages.$childIsoCode.po"]; + + $results = []; + + // Step 1: Search for all matches in messages.en.po. + $matchedMsgids = self::searchMsgidInFile($term, $files['en']); + + // Step 2: For each matched msgid, search for its translation in the other files. + foreach ($matchedMsgids as $msgid) { + $entry = [ + 'file' => $files['en'], + 'variable' => $msgid, + 'phpVarName' => self::stringToCamelCaseVariableName($msgid), + 'en' => self::getTranslationForVariable($msgid, $files['en']) + ]; + $entry[$parentIsoCode] = self::getTranslationForVariable($msgid, $files[$parentIsoCode]); + $entry[$childIsoCode] = self::getTranslationForVariable($msgid, $files[$childIsoCode], true); + + $results[] = $entry; + } + + return $results; + } + + /** + * Search for a specific term inside a given .po file and return the msgids that match. + * + * @param string $term The term to search for. + * @param string $filename The name of the .po file to search in. + * + * @return array An array of msgids that match the given term. + */ + private static function searchMsgidInFile($term, $filename) + { + $poFilePath = api_get_path(SYS_PATH) . self::LANGUAGE_TRANS_PATH . $filename; + $matchedMsgids = []; + + if (file_exists($poFilePath)) { + $lines = file($poFilePath, FILE_IGNORE_NEW_LINES); + $currentVariable = null; + + foreach ($lines as $line) { + if (strpos($line, 'msgid "') === 0) { + $currentVariable = str_replace('msgid "', '', $line); + $currentVariable = rtrim($currentVariable, '"'); + + if (stripos($currentVariable, $term) !== false) { + $matchedMsgids[] = $currentVariable; + } + } + } + } + + return $matchedMsgids; + } + + /** + * Retrieve the translation (msgstr) for a given variable (msgid) from a specified .po file. + * + * @param string $variable The variable (msgid) to search for. + * @param string $filename The name of the .po file to retrieve the translation from. + * + * @return string The translation (msgstr) for the provided variable, or an empty string if not found. + */ + private static function getTranslationForVariable(string $variable, string $filename, $checkSubLanguagePath = false): string + { + + $poFilePath = api_get_path(SYS_PATH) . self::LANGUAGE_TRANS_PATH . $filename; + if ($checkSubLanguagePath) { + $poFilePath = api_get_path(SYS_PATH) . self::SUBLANGUAGE_TRANS_PATH . $filename; + } + + if (file_exists($poFilePath)) { + $content = file_get_contents($poFilePath); + $pattern = '/msgid "' . preg_quote($variable, '/') . '"\nmsgstr "(.*?)"/'; + if (preg_match($pattern, $content, $match)) { + return $match[1]; + } + } + + return ''; + } + + /** + * Updates or adds a msgid in the specified .po file. + * + * @param string $filename Name of the .po file + * @param string $msgid Message identifier to search or add + * @param string $content Associated message content + * + * @return array Returns true if the operation was successful, otherwise returns false + */ + public static function updateOrAddMsgid($filename, $msgid, $content): array + { + $filePath = api_get_path(SYS_PATH) . self::SUBLANGUAGE_TRANS_PATH . $filename; + + if (!file_exists($filePath)) { + return ['success' => false, 'error' => 'File does not exist']; + } + + if (!is_writable($filePath)) { + try { + if (!chmod($filePath, 0664)) { + return ['success' => false, 'error' => 'Unable to set the file to writable']; + } + } catch (Exception $e) { + + return ['success' => false, 'error' => 'Failed to change file permissions: ' . $e->getMessage()]; + } + } + + $fileContents = file_get_contents($filePath); + if ($fileContents === false) { + return ['success' => false, 'error' => 'Failed to read file contents']; + } + + $pattern = '/msgid "' . preg_quote($msgid, '/') . '"' . PHP_EOL . 'msgstr "(.*?)"/'; + if (preg_match($pattern, $fileContents)) { + $replacement = 'msgid "' . $msgid . '"' . PHP_EOL . 'msgstr "' . $content . '"'; + $fileContents = preg_replace($pattern, $replacement, $fileContents); + } else { + $appendString = PHP_EOL . PHP_EOL . 'msgid "' . $msgid . '"' . PHP_EOL . 'msgstr "' . $content . '"'; + $fileContents .= $appendString; + } + + if (file_put_contents($filePath, $fileContents) === false) { + return ['success' => false, 'error' => 'Failed to write to file']; + } + + return ['success' => true]; + } + + /** + * Delete sub-language. + * In order to avoid deletion of main languages, we check the existence of a parent. + */ + public static function removeSubLanguage(int $parentId, int $subLanguageId): bool + { + $entityManager = Database::getManager(); + $subLanguage = $entityManager->getRepository(Language::class)->find($subLanguageId); + $parentLanguage = $subLanguage ? $subLanguage->getParent() : null; + + if (!$subLanguage || !$parentLanguage || $parentLanguage->getId() != $parentId) { + return false; + } + + // Locate and delete the .po file of the sub-language + $subLanguageIsoCode = $subLanguage->getIsocode(); + $poFilePath = api_get_path(SYS_PATH) . self::SUBLANGUAGE_TRANS_PATH . "messages.$subLanguageIsoCode.po"; + if (file_exists($poFilePath)) { + unlink($poFilePath); + } + + $entityManager->remove($subLanguage); + $entityManager->flush(); + + return true; + } + + /** + * Add a sub-language. + */ + public static function addSubLanguage(string $originalName, string $englishName, bool $isAvailable, int $parentId): bool|int + { + $entityManager = Database::getManager(); + $parentLanguage = $entityManager->getRepository(Language::class)->find($parentId); + if (!$parentLanguage) { + return false; + } + + $subLanguage = new Language(); + $subLanguage->setOriginalName($originalName) + ->setEnglishName($englishName) + ->setIsocode($englishName) + ->setAvailable($isAvailable) + ->setParent($parentLanguage); + + try { + $entityManager->persist($subLanguage); + $entityManager->flush(); + } catch (\Exception $e) { + // Handle exception if needed + return false; + } + + return $subLanguage->getId(); + } + + /** + * Remove a .po file for a sub-language. + * + * @param string $isoCode The ISO code of the sub-language (e.g., 'es_CO') + * + * @return bool True on success, false on failure + */ + public static function removePoFileForSubLanguage(string $isoCode): bool + { + if (empty($isoCode)) { + return false; + } + + // Path for the .po file you want to remove + $poFilePath = api_get_path(SYS_PATH) . self::SUBLANGUAGE_TRANS_PATH . "messages.$isoCode.po"; + + if (file_exists($poFilePath)) { + return unlink($poFilePath); + } + + // File does not exist, consider it a successful removal + return true; + } + + /** + * Check if a language exists by its ID. + */ + public static function languageExistsById(int $languageId): bool + { + $entityManager = Database::getManager(); + $language = $entityManager->getRepository(Language::class)->find($languageId); + + return $language !== null; + } + + /** + * Check if the given language is a parent of any sub-language. + */ + public static function isParentOfSubLanguage(int $parentId): bool + { + $entityManager = Database::getManager(); + $languageRepository = $entityManager->getRepository(Language::class); + + $childrenCount = $languageRepository->count(['parent' => $parentId]); + + return $childrenCount > 0; + } + + /** + * Get all information of a sub-language. + */ + public static function getAllInformationOfSubLanguage(int $parentId, int $subLanguageId): array + { + $entityManager = Database::getManager(); + $languageRepository = $entityManager->getRepository(Language::class); + + $subLanguage = $languageRepository->findOneBy([ + 'parent' => $parentId, + 'id' => $subLanguageId + ]); + + return $subLanguage ? self::convertLanguageToArray($subLanguage) : []; + } + + /** + * Convert a Language entity to an array. + */ + private static function convertLanguageToArray(Language $language): array + { + return [ + 'id' => $language->getId(), + 'original_name' => $language->getOriginalName(), + 'english_name' => $language->getEnglishName(), + 'isocode' => $language->getIsocode(), + 'available' => $language->getAvailable(), + // Add other fields as needed + ]; + } + + /** + * Check if a language exists. + */ + public static function checkIfLanguageExists(string $originalName, string $englishName, string $isoCode): array + { + $entityManager = Database::getManager(); + $languageRepository = $entityManager->getRepository(Language::class); + + $messageInformation = [ + 'original_name' => false, + 'english_name' => false, + 'isocode' => false, + 'execute_add' => true + ]; + + if ($languageRepository->count(['originalName' => $originalName]) > 0) { + $messageInformation['original_name'] = true; + $messageInformation['execute_add'] = false; + } + + if ($languageRepository->count(['englishName' => $englishName]) > 0) { + $messageInformation['english_name'] = true; + $messageInformation['execute_add'] = false; + } + + $isoList = api_get_platform_isocodes(); // Assuming this is an existing function + if (!in_array($isoCode, array_values($isoList))) { + $messageInformation['isocode'] = true; + $messageInformation['execute_add'] = false; + } + + return $messageInformation; + } + + /** + * Gets the ISO code of the parent language for a given language. + */ + public static function getParentLocale(string $childIsoCode): ?string + { + $em = Database::getManager(); + $languageRepository = $em->getRepository('Chamilo\CoreBundle\Entity\Language'); + + // Find the language by its ISO code + $language = $languageRepository->findOneBy(['isocode' => $childIsoCode]); + + if (!$language) { + return null; // Language not found + } + + // Get the parent language if it exists + $parentLanguage = $language->getParent(); + if ($parentLanguage) { + return $parentLanguage->getIsocode(); + } + + return null; // No parent language + } } diff --git a/src/CoreBundle/Command/UpdateVueTranslations.php b/src/CoreBundle/Command/UpdateVueTranslations.php index 8c2ed8b6be..4abded5e2d 100644 --- a/src/CoreBundle/Command/UpdateVueTranslations.php +++ b/src/CoreBundle/Command/UpdateVueTranslations.php @@ -6,6 +6,7 @@ declare(strict_types=1); namespace Chamilo\CoreBundle\Command; +use Chamilo\CoreBundle\Entity\Language; use Chamilo\CoreBundle\Repository\LanguageRepository; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; @@ -69,7 +70,8 @@ class UpdateVueTranslations extends Command $newLanguage = []; foreach ($translations as $variable => $translation) { - $translated = $this->translator->trans($variable, [], null, $iso); + //$translated = $this->translator->trans($variable, [], null, $iso); + $translated = $this->getTranslationWithFallback($variable, $language); $newLanguage[$variable] = $this->replaceMarkers($translated); } $newLanguageToString = json_encode($newLanguage, JSON_PRETTY_PRINT); @@ -83,6 +85,37 @@ class UpdateVueTranslations extends Command return Command::SUCCESS; } + /** + * Gets the translation for a given variable with fallbacks to parent language and base language. + * + * @param string $variable The variable to be translated. + * @param Language $language The Language entity for the current language. + * + * @return string The translated string. + */ + private function getTranslationWithFallback(string $variable, Language $language): string { + // Get the ISO code of the current language + $iso = $language->getIsocode(); + // Try to translate the variable in the current language + $translated = $this->translator->trans($variable, [], 'messages', $iso); + + // Check if the translation is not found and if there is a parent language + if ($translated === $variable && $language->getParent()) { + // Get the parent language entity and its ISO code + $parentLanguage = $language->getParent(); + $parentIso = $parentLanguage->getIsocode(); + // Try to translate the variable in the parent language + $translated = $this->translator->trans($variable, [], 'messages', $parentIso); + + // Check if translation is still not found and use the base language (English) + if ($translated === $variable) { + $translated = $this->translator->trans($variable, [], 'messages', 'en'); + } + } + + return $translated; + } + /** * Replace specifiers in a string to allow rendering them by i18n. *