1
0
mirror of https://github.com/processwire/processwire.git synced 2025-08-18 12:31:17 +02:00

Add support for common multi-language translations

This commit is contained in:
Ryan Cramer
2016-10-21 13:31:18 -04:00
parent 913201788c
commit 5005dfa259
3 changed files with 167 additions and 7 deletions

View File

@@ -38,7 +38,12 @@ function __($text, $textdomain = null, $context = '') {
if(is_null($textdomain)) $textdomain = 'site'; if(is_null($textdomain)) $textdomain = 'site';
} }
$value = htmlspecialchars($language->translator()->getTranslation($textdomain, $text, $context), ENT_QUOTES, 'UTF-8'); $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; return $value;
} }

View File

@@ -13,13 +13,17 @@ class LanguageTranslator extends Wire {
/** /**
* Language (Page) instance of the current language * Language (Page) instance of the current language
* *
* @var Language
*
*/ */
protected $currentLanguage = null; protected $currentLanguage;
/** /**
* Path where language files are stored * Path where language files are stored
* *
* i.e. language_files path for current $language * i.e. language_files path for current $language
*
* @var string
* *
*/ */
protected $path; protected $path;
@@ -27,12 +31,16 @@ class LanguageTranslator extends Wire {
/** /**
* Root path of installation, same as wire('config')->paths->root * Root path of installation, same as wire('config')->paths->root
* *
* @var string
*
*/ */
protected $rootPath; protected $rootPath;
/** /**
* Alternate root path for systems where there might be symlinks * Alternate root path for systems where there might be symlinks
* *
* @var string
*
*/ */
protected $rootPath2; protected $rootPath2;
@@ -50,26 +58,42 @@ class LanguageTranslator extends Wire {
* ) * )
* ); * );
* *
* @var array
*
*/ */
protected $textdomains = array(); protected $textdomains = array();
/** /**
* Cache of class names and the resulting textdomains * Cache of class names and the resulting textdomains
* *
* @var array
*
*/ */
protected $classNamesToTextdomains = array(); protected $classNamesToTextdomains = array();
/** /**
* Textdomains of parent classes that can be checked where applicable * Textdomains of parent classes that can be checked where applicable
* *
* @var array
*
*/ */
protected $parentTextdomains = 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 * Construct the translator and set the current language
* *
* @param Language $currentLanguage
*
*/ */
public function __construct(Language $currentLanguage) { public function __construct(Language $currentLanguage) {
$currentLanguage->wire($this); $currentLanguage->wire($this);
@@ -83,6 +107,9 @@ class LanguageTranslator extends Wire {
/** /**
* Set the current language and reset current stored textdomains * Set the current language and reset current stored textdomains
* *
* @param Language $language
* @return $this
*
*/ */
public function setCurrentLanguage(Language $language) { public function setCurrentLanguage(Language $language) {
@@ -93,6 +120,7 @@ class LanguageTranslator extends Wire {
// so if the language is changing, we clear out what's already in memory. // 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(); if($this->currentLanguage && $language->id != $this->currentLanguage->id) $this->textdomains = array();
$this->currentLanguage = $language; $this->currentLanguage = $language;
$this->isDefaultLanguage = $language->isDefault();
return $this; return $this;
} }
@@ -100,6 +128,11 @@ class LanguageTranslator extends Wire {
/** /**
* Return the array template for a textdomain, optionally populating it with data * 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()) { protected function textdomainTemplate($file = '', $textdomain = '', array $translations = array()) {
foreach($translations as $hash => $translation) { foreach($translations as $hash => $translation) {
@@ -119,6 +152,9 @@ class LanguageTranslator extends Wire {
* and then convert that to a textdomain string. Once determined, we cache it so that we * and then convert that to a textdomain string. Once determined, we cache it so that we
* don't have to do this again. * don't have to do this again.
* *
* @param Wire|object $o
* @return string
*
*/ */
protected function objectToTextdomain($o) { protected function objectToTextdomain($o) {
@@ -150,8 +186,9 @@ class LanguageTranslator extends Wire {
} }
} }
/** @var \ReflectionClass $parentClass */
while($parentClass = $reflection->getParentClass()) { while($parentClass = $reflection->getParentClass()) {
if(in_array($parentClass->getName(), $stopClasses)) break; if(in_array($parentClass->getShortName(), $stopClasses)) break;
$parentTextdomains[] = $this->filenameToTextdomain($parentClass->getFileName()); $parentTextdomains[] = $this->filenameToTextdomain($parentClass->getFileName());
$reflection = $parentClass; $reflection = $parentClass;
} }
@@ -202,6 +239,9 @@ class LanguageTranslator extends Wire {
* *
* This is determined by loading the textdomain and then grabbing the filename stored in the JSON properties * 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) { public function textdomainToFilename($textdomain) {
if(!isset($this->textdomains[$textdomain])) $this->loadTextdomain($textdomain); if(!isset($this->textdomains[$textdomain])) $this->loadTextdomain($textdomain);
@@ -259,6 +299,7 @@ class LanguageTranslator extends Wire {
// normalize textdomain to be a string, converting from filename or object if necessary // normalize textdomain to be a string, converting from filename or object if necessary
$textdomain = $this->textdomainString($textdomain); $textdomain = $this->textdomainString($textdomain);
$_text = $text;
// if the text is already provided in the proper language then no reason to go further // if the text is already provided in the proper language then no reason to go further
// if($this->currentLanguage->id == $this->defaultLanguagePageID) return $text; // if($this->currentLanguage->id == $this->defaultLanguagePageID) return $text;
@@ -285,7 +326,12 @@ class LanguageTranslator extends Wire {
break; 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 // if text hasn't changed at this point, we'll be returning it in the provided language since we have no translation
@@ -297,6 +343,9 @@ class LanguageTranslator extends Wire {
/** /**
* Return ALL translations for the given textdomain * Return ALL translations for the given textdomain
* *
* @param string $textdomain
* @return array
*
*/ */
public function getTranslations($textdomain) { public function getTranslations($textdomain) {
@@ -314,6 +363,12 @@ class LanguageTranslator extends Wire {
/** /**
* Set a translation * Set a translation
* *
* @param string $textdomain
* @param string $text
* @param string $translation
* @param string $context
* @return string
*
*/ */
public function setTranslation($textdomain, $text, $translation, $context = '') { public function setTranslation($textdomain, $text, $translation, $context = '') {
@@ -326,6 +381,11 @@ class LanguageTranslator extends Wire {
/** /**
* Set a translation using an already known hash * 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) { public function setTranslationFromHash($textdomain, $hash, $translation) {
@@ -347,7 +407,7 @@ class LanguageTranslator extends Wire {
* *
* @param string $textdomain * @param string $textdomain
* @param string $hash May be the translation hash or the translated text. * @param string $hash May be the translation hash or the translated text.
* @return this * @return $this
* *
*/ */
public function removeTranslation($textdomain, $hash) { public function removeTranslation($textdomain, $hash) {
@@ -375,6 +435,9 @@ class LanguageTranslator extends Wire {
/** /**
* Given original $text, issue a unique MD5 key used to reference it * Given original $text, issue a unique MD5 key used to reference it
* *
* @param string $text
* @return string
*
*/ */
protected function getTextHash($text) { protected function getTextHash($text) {
return md5($text); return md5($text);
@@ -383,6 +446,9 @@ class LanguageTranslator extends Wire {
/** /**
* Get the JSON filename where the current languages class translations are * Get the JSON filename where the current languages class translations are
* *
* @param string $textdomain
* @return string
*
*/ */
protected function getTextdomainTranslationFile($textdomain) { protected function getTextdomainTranslationFile($textdomain) {
$textdomain = $this->textdomainString($textdomain); $textdomain = $this->textdomainString($textdomain);
@@ -404,6 +470,9 @@ class LanguageTranslator extends Wire {
/** /**
* Load translation group $textdomain into the current language translations * Load translation group $textdomain into the current language translations
* *
* @param string $textdomain
* @return $this
*
*/ */
public function loadTextdomain($textdomain) { public function loadTextdomain($textdomain) {
@@ -479,6 +548,8 @@ class LanguageTranslator extends Wire {
/** /**
* Unload the given textdomain string from memory * Unload the given textdomain string from memory
* *
* @param string $textdomain
*
*/ */
public function unloadTextdomain($textdomain) { public function unloadTextdomain($textdomain) {
unset($this->textdomains[$textdomain]); unset($this->textdomains[$textdomain]);
@@ -487,6 +558,9 @@ class LanguageTranslator extends Wire {
/** /**
* Return the data available for the given $textdomain string * Return the data available for the given $textdomain string
* *
* @param string $textdomain
* @return array
*
*/ */
public function getTextdomain($textdomain) { public function getTextdomain($textdomain) {
$this->loadTextdomain($textdomain); $this->loadTextdomain($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;
}
} }

View File

@@ -364,6 +364,8 @@ class ProcessLanguageTranslator extends Process {
$field->attr('rows', 3); $field->attr('rows', 3);
} }
/** @var InputfieldText $field */
$field->attr('id+name', $hash); $field->attr('id+name', $hash);
$field->set('textFormat', Inputfield::textFormatNone); $field->set('textFormat', Inputfield::textFormatNone);
$field->attr('value', $translated); $field->attr('value', $translated);
@@ -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->skipLabel = Inputfield::skipLabelHeader;
$field->description = $untranslated; $field->description = $untranslated;