diff --git a/wire/core/LanguageFunctions.php b/wire/core/LanguageFunctions.php index e15bf02f..a13d991a 100644 --- a/wire/core/LanguageFunctions.php +++ b/wire/core/LanguageFunctions.php @@ -38,7 +38,12 @@ function __($text, $textdomain = null, $context = '') { if(is_null($textdomain)) $textdomain = 'site'; } $value = htmlspecialchars($language->translator()->getTranslation($textdomain, $text, $context), ENT_QUOTES, 'UTF-8'); - if($value === "=") $value = $text; + if($value === "=") { + $value = $text; + } else if($value === "+") { + $v = $language->translator()->commonTranslation($text); + $value = empty($v) ? $text : $v; + } return $value; } diff --git a/wire/modules/LanguageSupport/LanguageTranslator.php b/wire/modules/LanguageSupport/LanguageTranslator.php index b407d4a6..544644eb 100644 --- a/wire/modules/LanguageSupport/LanguageTranslator.php +++ b/wire/modules/LanguageSupport/LanguageTranslator.php @@ -12,26 +12,34 @@ class LanguageTranslator extends Wire { /** * Language (Page) instance of the current language + * + * @var Language * */ - protected $currentLanguage = null; + protected $currentLanguage; /** * Path where language files are stored * * i.e. language_files path for current $language + * + * @var string * */ protected $path; /** * Root path of installation, same as wire('config')->paths->root + * + * @var string * */ protected $rootPath; /** * Alternate root path for systems where there might be symlinks + * + * @var string * */ protected $rootPath2; @@ -49,26 +57,42 @@ class LanguageTranslator extends Wire { * ) * ) * ); + * + * @var array * */ protected $textdomains = array(); /** * Cache of class names and the resulting textdomains + * + * @var array * */ protected $classNamesToTextdomains = array(); /** * Textdomains of parent classes that can be checked where applicable + * + * @var array * */ protected $parentTextdomains = array( - // 'className' => array('parent textdomain 1', 'parent textdomain 2', 'etc.') - ); + // 'className' => array('parent textdomain 1', 'parent textdomain 2', 'etc.') + ); + + /** + * Is current language the default language? + * + * @var bool + * + */ + protected $isDefaultLanguage = false; /** * Construct the translator and set the current language + * + * @param Language $currentLanguage * */ public function __construct(Language $currentLanguage) { @@ -82,6 +106,9 @@ class LanguageTranslator extends Wire { /** * Set the current language and reset current stored textdomains + * + * @param Language $language + * @return $this * */ public function setCurrentLanguage(Language $language) { @@ -93,12 +120,18 @@ class LanguageTranslator extends Wire { // so if the language is changing, we clear out what's already in memory. if($this->currentLanguage && $language->id != $this->currentLanguage->id) $this->textdomains = array(); $this->currentLanguage = $language; + $this->isDefaultLanguage = $language->isDefault(); return $this; } /** * Return the array template for a textdomain, optionally populating it with data + * + * @param string $file + * @param string $textdomain + * @param array $translations + * @return array * */ protected function textdomainTemplate($file = '', $textdomain = '', array $translations = array()) { @@ -118,6 +151,9 @@ class LanguageTranslator extends Wire { * This is accomplished with PHP's ReflectionClass to determine the file where the class lives * and then convert that to a textdomain string. Once determined, we cache it so that we * don't have to do this again. + * + * @param Wire|object $o + * @return string * */ protected function objectToTextdomain($o) { @@ -150,8 +186,9 @@ class LanguageTranslator extends Wire { } } + /** @var \ReflectionClass $parentClass */ while($parentClass = $reflection->getParentClass()) { - if(in_array($parentClass->getName(), $stopClasses)) break; + if(in_array($parentClass->getShortName(), $stopClasses)) break; $parentTextdomains[] = $this->filenameToTextdomain($parentClass->getFileName()); $reflection = $parentClass; } @@ -201,6 +238,9 @@ class LanguageTranslator extends Wire { * Given a textdomain string, convert it to a filename (relative to site root) * * This is determined by loading the textdomain and then grabbing the filename stored in the JSON properties + * + * @param string $textdomain + * @return string * */ public function textdomainToFilename($textdomain) { @@ -259,6 +299,7 @@ class LanguageTranslator extends Wire { // normalize textdomain to be a string, converting from filename or object if necessary $textdomain = $this->textdomainString($textdomain); + $_text = $text; // if the text is already provided in the proper language then no reason to go further // if($this->currentLanguage->id == $this->defaultLanguagePageID) return $text; @@ -285,7 +326,12 @@ class LanguageTranslator extends Wire { break; } } - + } + + // see if text is available as a common translation + if($text === $_text && !$this->isDefaultLanguage) { + $_text = $this->commonTranslation($text); + if(!empty($_text)) $text = $_text; } // if text hasn't changed at this point, we'll be returning it in the provided language since we have no translation @@ -296,6 +342,9 @@ class LanguageTranslator extends Wire { /** * Return ALL translations for the given textdomain + * + * @param string $textdomain + * @return array * */ public function getTranslations($textdomain) { @@ -313,6 +362,12 @@ class LanguageTranslator extends Wire { /** * Set a translation + * + * @param string $textdomain + * @param string $text + * @param string $translation + * @param string $context + * @return string * */ public function setTranslation($textdomain, $text, $translation, $context = '') { @@ -325,6 +380,11 @@ class LanguageTranslator extends Wire { /** * Set a translation using an already known hash + * + * @param string $textdomain + * @param string $hash + * @param string $translation + * @return string * */ public function setTranslationFromHash($textdomain, $hash, $translation) { @@ -347,7 +407,7 @@ class LanguageTranslator extends Wire { * * @param string $textdomain * @param string $hash May be the translation hash or the translated text. - * @return this + * @return $this * */ public function removeTranslation($textdomain, $hash) { @@ -374,6 +434,9 @@ class LanguageTranslator extends Wire { /** * Given original $text, issue a unique MD5 key used to reference it + * + * @param string $text + * @return string * */ protected function getTextHash($text) { @@ -382,6 +445,9 @@ class LanguageTranslator extends Wire { /** * Get the JSON filename where the current languages class translations are + * + * @param string $textdomain + * @return string * */ protected function getTextdomainTranslationFile($textdomain) { @@ -403,6 +469,9 @@ class LanguageTranslator extends Wire { /** * Load translation group $textdomain into the current language translations + * + * @param string $textdomain + * @return $this * */ public function loadTextdomain($textdomain) { @@ -478,6 +547,8 @@ class LanguageTranslator extends Wire { /** * Unload the given textdomain string from memory + * + * @param string $textdomain * */ public function unloadTextdomain($textdomain) { @@ -486,6 +557,9 @@ class LanguageTranslator extends Wire { /** * Return the data available for the given $textdomain string + * + * @param string $textdomain + * @return array * */ public function getTextdomain($textdomain) { @@ -508,6 +582,69 @@ class LanguageTranslator extends Wire { } } + /** + * Get a common translation + * + * These are commonly used translations that can be used as fallbacks. + * + * Returns blank string if given string is not a common phrase. + * Returns given $str if given string is common, but not translated here. + * Returns translated $str if common and translated. + * + * @param string $str + * @return string + * + */ + public function commonTranslation($str) { + + static $level = 0; + if(strlen($str) >= 15 || $level) return ''; // 15=max length of our common phrases + $level++; + $v = ''; + + switch(strtolower($str)) { + case 'edit': $v = $this->_('Edit'); break; + case 'delete': $v = $this->_('Delete'); break; + case 'save': $v = $this->_('Save'); break; + case 'save & exit': + case 'save and exit': + case 'save + exit': $v = $this->_('Save + Exit'); break; + case 'cancel': $v = $this->_('Cancel'); break; + case 'new': $v = $this->_('New'); break; + case 'add': $v = $this->_('Add'); break; + case 'add new': $v = $this->_('Add New'); break; + case 'are you sure?': $v = $this->_('Are you sure?'); break; + case 'confirm': $v = $this->_('Confirm'); break; + case 'import': $v = $this->_('Import'); break; + case 'export': $v = $this->_('Export'); break; + case 'yes': $v = $this->_('Yes'); break; + case 'no': $v = $this->_('No'); break; + case 'on': $v = $this->_('On'); break; + case 'off': $v = $this->_('Off'); break; + case 'enabled': $v = $this->_('Enabled'); break; + case 'disabled': $v = $this->_('Disabled'); break; + case 'example': $v = $this->_('Example'); break; + case 'please note': $v = $this->_('Please note:'); break; + case 'note': $v = $this->_('Note'); break; + case 'notes': $v = $this->_('Notes'); break; + case 'settings': $v = $this->_('Settings'); break; + case 'type': $v = $this->_('Type'); break; + case 'label': $v = $this->_('Label'); break; + case 'name': $v = $this->_('Name'); break; + case 'description': $v = $this->_('Description'); break; + case 'details': $v = $this->_('Details'); break; + case 'access': $v = $this->_('Access'); break; + case 'advanced': $v = $this->_('Advanced'); break; + case 'icon': $v = $this->_('Icon'); break; + case 'system': $v = $this->_('System'); break; + case 'modified': $v = $this->_('Modified'); break; + case 'error': $v = $this->_('Error'); break; + } + + $level--; + return $v; + } + } diff --git a/wire/modules/LanguageSupport/ProcessLanguageTranslator.module b/wire/modules/LanguageSupport/ProcessLanguageTranslator.module index 778e2136..c7b48d2d 100644 --- a/wire/modules/LanguageSupport/ProcessLanguageTranslator.module +++ b/wire/modules/LanguageSupport/ProcessLanguageTranslator.module @@ -363,6 +363,8 @@ class ProcessLanguageTranslator extends Process { $field = $this->modules->get("InputfieldTextarea"); $field->attr('rows', 3); } + + /** @var InputfieldText $field */ $field->attr('id+name', $hash); $field->set('textFormat', Inputfield::textFormatNone); @@ -385,6 +387,22 @@ class ProcessLanguageTranslator extends Process { } } + if((empty($translated) || $translated === '+') && !$field instanceof InputfieldTextarea) { + $languages = $this->wire('languages'); + $languages->setLanguage($this->language); + $this->wire('user')->language = $this->language; + $test = $this->translator->commonTranslation($untranslated); + $field->textFormat = Inputfield::textFormatBasic; + if(strlen($test) && $test !== $untranslated) { + $field->notes = trim($field->notes . "\n" . + sprintf($this->_('If no text is provided, the common translation “**%s**” will be used.'), $test)) . " " . + $this->_('Enter a single plus sign “**+**” above to also mark this field as translated.'); + + $field->attr('placeholder', $test); + } + $languages->unsetLanguage(); + } + $field->skipLabel = Inputfield::skipLabelHeader; $field->description = $untranslated;