diff --git a/classes/Language.php b/classes/Language.php index 0963279bb..e95fd4631 100644 --- a/classes/Language.php +++ b/classes/Language.php @@ -82,6 +82,7 @@ class LanguageCore extends ObjectModel 'errors' => '_ERRORS', 'admin' => '_LANGADM', 'pdf' => '_LANGPDF', + 'class' => '_LANGCLASS', 'tabs' => 'tabs', ); @@ -275,7 +276,7 @@ class LanguageCore extends ObjectModel $mPath_to = _PS_MAIL_DIR_.(string)$iso_to.'/'; } - $lFiles = array('admin.php', 'errors.php', 'fields.php', 'pdf.php', 'tabs.php'); + $lFiles = array('admin.php', 'errors.php', 'fields.php', 'pdf.php', 'class.php', 'tabs.php'); // Added natives mails files $mFiles = array( diff --git a/classes/ObjectModel.php b/classes/ObjectModel.php index e960ee837..abc88c3db 100644 --- a/classes/ObjectModel.php +++ b/classes/ObjectModel.php @@ -1122,6 +1122,25 @@ abstract class ObjectModelCore implements Core_Foundation_Database_EntityInterfa return ((is_array($_FIELDS) && array_key_exists($key, $_FIELDS)) ? ($htmlentities ? htmlentities($_FIELDS[$key], ENT_QUOTES, 'utf-8') : $_FIELDS[$key]) : $field); } + /** + * Get a translation for this core class. + * Strings translated via Localization > Translations > Core class translations. + * + * @param string $string String to translate (must be a literal for the scanner) + * @param string $class Class context (defaults to calling class) + * @return string + */ + public static function l($string, $class = null) + { + if (!$class) { + $class = get_called_class(); + if (substr($class, -4) === 'Core') { + $class = substr($class, 0, -4); + } + } + return Translate::getClassTranslation($string, $class); + } + /** * @deprecated 1.5.0.1 Use validateController() instead * @param bool $htmlentities diff --git a/classes/Translate.php b/classes/Translate.php index 7fac82462..bdfb90b6a 100644 --- a/classes/Translate.php +++ b/classes/Translate.php @@ -275,6 +275,42 @@ class TranslateCore return $str; } + /** + * Get a translation for a core class (used via ObjectModel::l()) + * + * @param string $string String to translate + * @param string $class Class name (e.g. 'Order', 'HotelBookingDetail') + * @param bool $addslashes + * @param bool $htmlentities + * @return string + */ + public static function getClassTranslation($string, $class = '', $addslashes = false, $htmlentities = false) + { + global $_LANGCLASS; + + $iso = Context::getContext()->language->iso_code; + if (empty($iso)) { + $iso = Language::getIsoById((int)Configuration::get('PS_LANG_DEFAULT')); + } + + $file = _PS_TRANSLATIONS_DIR_.$iso.'/class.php'; + if (!isset($_LANGCLASS) && file_exists($file)) { + include_once($file); + } + + $string = preg_replace("/\\\*'/", "\'", $string); + $key = md5($string); + + $str = (isset($_LANGCLASS) && is_array($_LANGCLASS) && array_key_exists($class.$key, $_LANGCLASS)) + ? $_LANGCLASS[$class.$key] + : $string; + + if ($addslashes) { + return addslashes($str); + } + return $htmlentities ? htmlspecialchars(stripslashes($str), ENT_QUOTES, 'utf-8') : stripslashes($str); + } + /** * Check if string use a specif syntax for sprintf and replace arguments if use it * @@ -391,6 +427,12 @@ class TranslateCore $regex = '/\{l\s*s=([\'\"])'._PS_TRANS_PATTERN_.'\1(\s*sprintf=.*)?(\s*js=1)?(\s*pdf=\'true\')?\s*\}/U'; } break; + + case 'class': + // Parsing core class files for self::l() calls only + // ClassName::l() is intentionally excluded to avoid matching Mail::l() and similar + $regex = '/(?:self)::l\((\')'._PS_TRANS_PATTERN_.'\'[\)|\,]/U'; + break; } if (!is_array($regex)) { diff --git a/classes/order/Order.php b/classes/order/Order.php index b28d9bdea..45a72abec 100644 --- a/classes/order/Order.php +++ b/classes/order/Order.php @@ -2506,100 +2506,100 @@ class OrderCore extends ObjectModel { $guestRegCardInfo = array( self::GRC_SECTION_GUEST_INFO => array( - 'name' => 'Guest Information', + 'name' => self::l('Guest Information'), 'fields' => array( - self::GRC_GUEST_TITLE => 'Title', - self::GRC_GUEST_FULL_NAME => 'Full Name (As per ID)', - self::GRC_GUEST_PHONE => 'Phone / Mobile', - self::GRC_GUEST_EMAIL => 'Email', - self::GRC_GUEST_DOB => 'Date of Birth', - self::GRC_GUEST_NATIONALITY => 'Nationality', - self::GRC_GUEST_CITY_COUNTRY => 'City / Country', - self::GRC_GUEST_POSTAL_CODE => 'Postal Code', - self::GRC_GUEST_ADDRESS => 'Address', + self::GRC_GUEST_TITLE => self::l('Title'), + self::GRC_GUEST_FULL_NAME => self::l('Full Name (As per ID)'), + self::GRC_GUEST_PHONE => self::l('Phone / Mobile'), + self::GRC_GUEST_EMAIL => self::l('Email'), + self::GRC_GUEST_DOB => self::l('Date of Birth'), + self::GRC_GUEST_NATIONALITY => self::l('Nationality'), + self::GRC_GUEST_CITY_COUNTRY => self::l('City / Country'), + self::GRC_GUEST_POSTAL_CODE => self::l('Postal Code'), + self::GRC_GUEST_ADDRESS => self::l('Address'), ), ), self::GRC_SECTION_TRAVEL_INFO => array( - 'name' => 'Travel Information', + 'name' => self::l('Travel Information'), 'fields' => array( - self::GRC_TRAVEL_ARRIVED_FROM => 'Arrived From', - self::GRC_TRAVEL_NEXT_DESTINATION => 'Next Destination', - self::GRC_TRAVEL_FLIGHT_TRAIN => 'Flight / Train Number', - self::GRC_TRAVEL_VEHICLE_REG => 'Vehicle Reg. No.', - self::GRC_TRAVEL_PURPOSE_OF_VISIT => 'Purpose of Visit', + self::GRC_TRAVEL_ARRIVED_FROM => self::l('Arrived From'), + self::GRC_TRAVEL_NEXT_DESTINATION => self::l('Next Destination'), + self::GRC_TRAVEL_FLIGHT_TRAIN => self::l('Flight / Train Number'), + self::GRC_TRAVEL_VEHICLE_REG => self::l('Vehicle Reg. No.'), + self::GRC_TRAVEL_PURPOSE_OF_VISIT => self::l('Purpose of Visit'), ), ), self::GRC_SECTION_BOOKING_INFO => array( - 'name' => 'Booking Information', + 'name' => self::l('Booking Information'), 'fields' => array( - self::GRC_BOOKING_REFERENCE => 'Booking Reference No.', - self::GRC_BOOKING_RATE_PER_NIGHT => 'Rate per Night', - self::GRC_BOOKING_ARRIVAL => 'Arrival Date & Time', - self::GRC_BOOKING_DEPARTURE => 'Departure Date & Time', - self::GRC_BOOKING_ROOM_TYPE => 'Room Type', - self::GRC_BOOKING_ROOM_NUMBER => 'Room Number', - self::GRC_BOOKING_NUM_GUESTS => 'Number of Guests', + self::GRC_BOOKING_REFERENCE => self::l('Booking Reference No.'), + self::GRC_BOOKING_RATE_PER_NIGHT => self::l('Rate per Night'), + self::GRC_BOOKING_ARRIVAL => self::l('Arrival Date & Time'), + self::GRC_BOOKING_DEPARTURE => self::l('Departure Date & Time'), + self::GRC_BOOKING_ROOM_TYPE => self::l('Room Type'), + self::GRC_BOOKING_ROOM_NUMBER => self::l('Room Number'), + self::GRC_BOOKING_NUM_GUESTS => self::l('Number of Guests'), ), ), self::GRC_SECTION_IDENTIFICATION => array( - 'name' => 'Identification Document', + 'name' => self::l('Identification Document'), 'fields' => array( - self::GRC_ID_IDENTITY_PROOF => 'Identity Proof', - self::GRC_ID_NUMBER => 'ID Number', - self::GRC_ID_PASSPORT_NO => 'Passport No.', - self::GRC_ID_PLACE_OF_ISSUE => 'Place of Issue', - self::GRC_ID_DATE_OF_ISSUE => 'Date of Issue', - self::GRC_ID_DATE_OF_EXPIRY => 'Date of Expiry', - self::GRC_ID_VISA_NUMBER => 'Visa Number', - self::GRC_ID_VALID_UNTIL => 'Valid Until', - self::GRC_ID_ARRIVAL_DATE_IN_COUNTRY => 'Arrival Date in Country', + self::GRC_ID_IDENTITY_PROOF => self::l('Identity Proof'), + self::GRC_ID_NUMBER => self::l('ID Number'), + self::GRC_ID_PASSPORT_NO => self::l('Passport No.'), + self::GRC_ID_PLACE_OF_ISSUE => self::l('Place of Issue'), + self::GRC_ID_DATE_OF_ISSUE => self::l('Date of Issue'), + self::GRC_ID_DATE_OF_EXPIRY => self::l('Date of Expiry'), + self::GRC_ID_VISA_NUMBER => self::l('Visa Number'), + self::GRC_ID_VALID_UNTIL => self::l('Valid Until'), + self::GRC_ID_ARRIVAL_DATE_IN_COUNTRY => self::l('Arrival Date in Country'), ), ), self::GRC_SECTION_ADDITIONAL_GUESTS => array( - 'name' => 'Additional Guests', + 'name' => self::l('Additional Guests'), 'fields' => array( - self::GRC_ADD_GUEST_NAME => 'Guest Name', - self::GRC_ADD_GUEST_ID_TYPE => 'ID Type', - self::GRC_ADD_GUEST_ID_NUMBER => 'ID Number', - self::GRC_ADD_GUEST_NATIONALITY => 'Nationality', + self::GRC_ADD_GUEST_NAME => self::l('Guest Name'), + self::GRC_ADD_GUEST_ID_TYPE => self::l('ID Type'), + self::GRC_ADD_GUEST_ID_NUMBER => self::l('ID Number'), + self::GRC_ADD_GUEST_NATIONALITY => self::l('Nationality'), ), ), self::GRC_SECTION_BILLING_CORPORATE => array( - 'name' => 'Billing & Corporate Details', + 'name' => self::l('Billing & Corporate Details'), 'fields' => array( - self::GRC_BILLING_COMPANY => 'Company / Agent', - self::GRC_BILLING_TAX_ID => 'Tax ID / VAT No.', + self::GRC_BILLING_COMPANY => self::l('Company / Agent'), + self::GRC_BILLING_TAX_ID => self::l('Tax ID / VAT No.'), ), ), self::GRC_SECTION_PAYMENT_DEPOSIT => array( - 'name' => 'Payment & Deposit', + 'name' => self::l('Payment & Deposit'), 'fields' => array( - self::GRC_PAYMENT_METHOD => 'Payment Method', - self::GRC_PAYMENT_CARD_NUMBER => 'Credit Card Number', - self::GRC_PAYMENT_SECURITY_DEPOSIT => 'Security Deposit', + self::GRC_PAYMENT_METHOD => self::l('Payment Method'), + self::GRC_PAYMENT_CARD_NUMBER => self::l('Credit Card Number'), + self::GRC_PAYMENT_SECURITY_DEPOSIT => self::l('Security Deposit'), ), ), self::GRC_SECTION_GUEST_SIGNATURE => array( - 'name' => 'Guest Signature', + 'name' => self::l('Guest Signature'), 'fields' => array( - self::GRC_SIG_SIGNATURE => 'Signature', - self::GRC_SIG_DATE => 'Date', + self::GRC_SIG_SIGNATURE => self::l('Signature'), + self::GRC_SIG_DATE => self::l('Date'), ), ), self::GRC_SECTION_PROPERTY_REGS => array( - 'name' => 'Property Regulations', + 'name' => self::l('Property Regulations'), 'fields' => array( - self::GRC_PROP_CHECKIN_CHECKOUT_TIME => 'Check-in / Check-out Time', - self::GRC_PROP_HOTEL_POLICIES => 'Hotel Policies', + self::GRC_PROP_CHECKIN_CHECKOUT_TIME => self::l('Check-in / Check-out Time'), + self::GRC_PROP_HOTEL_POLICIES => self::l('Hotel Policies'), ), ), self::GRC_SECTION_OFFICE_USE_ONLY => array( - 'name' => 'For Office Use Only', + 'name' => self::l('For Office Use Only'), 'fields' => array( - self::GRC_OFFICE_STAFF_NAME => 'Staff Name', - self::GRC_OFFICE_CHECKIN_TIME => 'Check-in Time', - self::GRC_OFFICE_ID_VERIFIED => 'ID Verified', - self::GRC_OFFICE_REG_NO => 'Registration No.', + self::GRC_OFFICE_STAFF_NAME => self::l('Staff Name'), + self::GRC_OFFICE_CHECKIN_TIME => self::l('Check-in Time'), + self::GRC_OFFICE_ID_VERIFIED => self::l('ID Verified'), + self::GRC_OFFICE_REG_NO => self::l('Registration No.'), ), ), ); diff --git a/classes/pdf/HTMLTemplateGuestRegistrationForm.php b/classes/pdf/HTMLTemplateGuestRegistrationForm.php index 43f035636..ad6790244 100644 --- a/classes/pdf/HTMLTemplateGuestRegistrationForm.php +++ b/classes/pdf/HTMLTemplateGuestRegistrationForm.php @@ -50,7 +50,7 @@ class HTMLTemplateGuestRegistrationFormCore extends HTMLTemplate public function getHeader() { $this->assignCommonHeaderData(); - $this->smarty->assign(array('header' => self::l('Guest Registration Card'))); + $this->smarty->assign(array('header' => HTMLTemplateGuestRegistrationForm::l('Guest Registration Card'))); return $this->smarty->fetch($this->getTemplate('header')); } diff --git a/controllers/admin/AdminTranslationsController.php b/controllers/admin/AdminTranslationsController.php index e56831c50..f1c3cab26 100644 --- a/controllers/admin/AdminTranslationsController.php +++ b/controllers/admin/AdminTranslationsController.php @@ -333,6 +333,10 @@ class AdminTranslationsControllerCore extends AdminController $to_insert = $GLOBALS['_LANGPDF']; break; + case 'class': + $to_insert = $GLOBALS['_LANGCLASS']; + break; + } foreach ($to_insert as $key => $value) { if (in_array($key, $keysToUpdate)) { @@ -1229,6 +1233,20 @@ class AdminTranslationsControllerCore extends AdminController $directories['php'] = Translate::listFiles(_PS_CLASS_DIR_, array(), 'php'); break; + case 'class': + $directories['php'] = Translate::listFiles(_PS_CLASS_DIR_, array(), 'php'); + $directories['php'] = array_merge($directories['php'], Translate::listFiles(_PS_OVERRIDE_DIR_.'classes/', array(), 'php')); + // Remove classes/pdf/ and classes/helper/ — those subdirs have dedicated translation + // types (pdf and back) that write to pdf.php and admin.php respectively. + // self::l() there resolves to HTMLTemplate::l() / Helper::l(), not ObjectModel::l(). + foreach (array_keys($directories['php']) as $dir) { + if (strpos($dir, DIRECTORY_SEPARATOR.'pdf'.DIRECTORY_SEPARATOR) !== false + || strpos($dir, DIRECTORY_SEPARATOR.'helper'.DIRECTORY_SEPARATOR) !== false) { + unset($directories['php'][$dir]); + } + } + break; + case 'pdf': $tpl_theme = Tools::file_exists_cache(_PS_THEME_SELECTED_DIR_.'pdf/') ? scandir(_PS_THEME_SELECTED_DIR_.'pdf/') : array(); $directories = array( @@ -1317,6 +1335,12 @@ class AdminTranslationsControllerCore extends AdminController 'dir' => _PS_TRANSLATIONS_DIR_.$this->lang_selected->iso_code.'/', 'file' => 'pdf.php' ), + 'class' => array( + 'name' => $this->l('Core class translations'), + 'var' => '_LANGCLASS', + 'dir' => _PS_TRANSLATIONS_DIR_.$this->lang_selected->iso_code.'/', + 'file' => 'class.php' + ), 'mails' => array( 'name' => $this->l('Email templates translations'), 'var' => '_LANGMAIL', @@ -1474,7 +1498,7 @@ class AdminTranslationsControllerCore extends AdminController } else { $this->errors[] = Tools::displayError('You do not have permission to edit this.'); } - } elseif (Tools::isSubmit('submitTranslationsBack') || Tools::isSubmit('submitTranslationsErrors') || Tools::isSubmit('submitTranslationsFields') || Tools::isSubmit('submitTranslationsFront')) { + } elseif (Tools::isSubmit('submitTranslationsBack') || Tools::isSubmit('submitTranslationsErrors') || Tools::isSubmit('submitTranslationsFields') || Tools::isSubmit('submitTranslationsFront') || Tools::isSubmit('submitTranslationsClass')) { if ($this->tabAccess['edit'] === 1) { $this->writeTranslationFile(); } else { @@ -1812,6 +1836,65 @@ class AdminTranslationsControllerCore extends AdminController /** * This method generate the form for back translations */ + public function initFormClass() + { + $name_var = $this->translations_informations[$this->type_selected]['var']; + $GLOBALS[$name_var] = $this->fileExists(); + $missing_translations = array(); + $missing_translations_found = array(); + $tabs_array = array(); + + $files_per_directory = $this->getFileToParseByTypeTranslation(); + + foreach ($files_per_directory['php'] as $dir => $files) { + foreach ($files as $file) { + if (preg_match('/^(.*)\.php$/', $file) && Tools::file_exists_cache($file_path = $dir.$file) && !in_array($file, Translate::$ignore_folder)) { + $prefix_key = basename($file, '.php'); + + $content = file_get_contents($file_path); + $matches = Translate::userParseFile($content, $this->type_selected, 'php'); + + foreach ($matches as $key) { + if (isset($GLOBALS[$name_var][$prefix_key.md5($key)])) { + $tabs_array[$prefix_key][$key]['trad'] = stripslashes(html_entity_decode($GLOBALS[$name_var][$prefix_key.md5($key)], ENT_COMPAT, 'UTF-8')); + } else { + if (!isset($tabs_array[$prefix_key][$key]['trad'])) { + $tabs_array[$prefix_key][$key]['trad'] = ''; + if (!isset($missing_translations[$prefix_key])) { + $missing_translations[$prefix_key] = 1; + $missing_translations_found[$prefix_key] = array(); + $missing_translations_found[$prefix_key][] = $key; + } elseif (!in_array($key, $missing_translations_found[$prefix_key])) { + $missing_translations[$prefix_key]++; + $missing_translations_found[$prefix_key][] = $key; + } + } + } + $tabs_array[$prefix_key][$key]['use_sprintf'] = Translate::checkIfKeyUseSprintf($key); + } + } + } + } + + $count = 0; + foreach ($tabs_array as $array) { + $count += count($array); + } + + $this->tpl_view_vars = array_merge($this->tpl_view_vars, array( + 'count' => $count, + 'cancel_url' => $this->context->link->getAdminLink('AdminTranslations'), + 'limit_warning' => $this->displayLimitPostWarning($count), + 'mod_security_warning' => Tools::apacheModExists('mod_security'), + 'tabsArray' => $tabs_array, + 'missing_translations' => $missing_translations + )); + + $this->initToolbar(); + $this->base_tpl_view = 'translation_form.tpl'; + return parent::renderView(); + } + public function initFormBack() { $name_var = $this->translations_informations[$this->type_selected]['var'];