diff --git a/core/Requirements.php b/core/Requirements.php index 98bf617c5..3811e3c70 100644 --- a/core/Requirements.php +++ b/core/Requirements.php @@ -644,17 +644,20 @@ class Requirements_Backend { * @param $langDir The javascript lang directory, relative to the site root, e.g., 'sapphire/javascript/lang' */ public function add_i18n_javascript($langDir) { - // Include i18n.js even if no languages are found. The fact that - // add_i18n_javascript() was called indicates that the methods in - // here are needed. - $this->javascript(SAPPHIRE_DIR . '/javascript/i18n.js'); + if(i18n::get_js_i18n()) { + // Include i18n.js even if no languages are found. The fact that + // add_i18n_javascript() was called indicates that the methods in + // here are needed. + $this->javascript(SAPPHIRE_DIR . '/javascript/i18n.js'); - if(substr($langDir,-1) != '/') $langDir .= '/'; + if(substr($langDir,-1) != '/') $langDir .= '/'; - $defaultLangPath = $langDir . i18n::default_locale() . '.js'; - $langPath = $langDir . i18n::get_locale() . '.js'; - foreach(array($defaultLangPath, $langPath) as $path) { - if(Director::fileExists($path)) $this->javascript($path); + $this->javascript($langDir . i18n::default_locale() . '.js'); + $this->javascript($langDir . i18n::get_locale() . '.js'); + + // Stub i18n implementation for when i18n is disabled. + } else { + $this->javascript[SAPPHIRE_DIR . '/javascript/i18nx.js'] = true; } } diff --git a/core/i18n.php b/core/i18n.php index dd9d9fbd6..8734a8048 100755 --- a/core/i18n.php +++ b/core/i18n.php @@ -59,6 +59,38 @@ class i18n extends Object { * @var string */ protected static $default_locale = 'en_US'; + + /** + * @var boolean + */ + protected static $js_i18n = true; + + /** + * Use javascript i18n through the ss.i18n class (enabled by default). + * If set to TRUE, includes javascript requirements for the base library + * (sapphire/javascript/i18n.js) and all necessary lang files (e.g. sapphire/lang/de_DE.js) + * plus fallbacks to the default locale (e.g. sapphire/lang/en_US.js). + * If set to FALSE, only includes a stub implementation + * which is necessary. Mainly disabled to save bandwidth + * in a frontend context when website is in single language. + * + * Caution: This flag gets overwritten in {@link LeftAndMain::init()} to enforce javascript + * i18n for the CMS interfaces. + * + * @see Requirements::process_i18n_javascript() + * + * @param bool $bool + */ + public static function set_js_i18n($bool) { + self::$js_i18n = $bool; + } + + /** + * @return bool + */ + public static function get_js_i18n() { + return self::$js_i18n; + } /** * An exhaustive list of possible locales (code => language and country) diff --git a/javascript/HasManyFileField.js b/javascript/HasManyFileField.js index b8b223fae..abcee4f1b 100644 --- a/javascript/HasManyFileField.js +++ b/javascript/HasManyFileField.js @@ -84,7 +84,7 @@ HasManyFileFieldUploadButton.prototype = { uploadFileQueuedCallback: function(file,queueLength) { var message = ss.i18n.sprintf( - ss.i18n._t('HASMANYFILEFIELD.UPLOADING'), + ss.i18n._t('HASMANYFILEFIELD.UPLOADING', 'Uploading... %s'), this.upload.getFilesToUpload() ); this.parentNode.parentNode.uploadMessage.innerHTML = message; @@ -92,7 +92,7 @@ HasManyFileFieldUploadButton.prototype = { uploadFileCompleteCallback: function(file,serverData) { var message = ss.i18n.sprintf( - ss.i18n._t('HASMANYFILEFIELD.UPLOADING'), + ss.i18n._t('HASMANYFILEFIELD.UPLOADING', 'Uploading... %s'), this.upload.getFilesUploaded() + "/" + this.upload.getFilesToUpload() ); this.parentNode.parentNode.uploadMessage.innerHTML = message; diff --git a/javascript/TableField.js b/javascript/TableField.js index 2fa831c0b..673f7a191 100755 --- a/javascript/TableField.js +++ b/javascript/TableField.js @@ -75,7 +75,7 @@ TableField.prototype = { } // TODO ajaxErrorHandler and loading-image are dependent on cms, but formfield is in sapphire - var confirmed = confirm(ss.i18n._t('TABLEFIELD.DELETECONFIRMMESSAGE')); + var confirmed = confirm(ss.i18n._t('TABLEFIELD.DELETECONFIRMMESSAGE', 'Are you sure you want to delete this record?')); if(confirmed){ img.setAttribute("src",'cms/images/network-save.gif'); // TODO doesn't work new Ajax.Request( diff --git a/javascript/TableListField.js b/javascript/TableListField.js index 8776865a8..576c29581 100755 --- a/javascript/TableListField.js +++ b/javascript/TableListField.js @@ -84,7 +84,7 @@ TableListField.prototype = { var row = Event.findElement(e,"tr"); // TODO ajaxErrorHandler and loading-image are dependent on cms, but formfield is in sapphire - var confirmed = confirm(ss.i18n._t('TABLEFIELD.DELETECONFIRMMESSAGE')); + var confirmed = confirm(ss.i18n._t('TABLEFIELD.DELETECONFIRMMESSAGE', 'Are you sure you want to delete this record?')); if(confirmed) { img.setAttribute("src",'cms/images/network-save.gif'); // TODO doesn't work diff --git a/javascript/TreeSelectorField.js b/javascript/TreeSelectorField.js index 13d5c17a0..8e0917ac1 100755 --- a/javascript/TreeSelectorField.js +++ b/javascript/TreeSelectorField.js @@ -67,7 +67,7 @@ TreeDropdownField.prototype = { } this.itemTree.className = 'tree_holder'; - this.itemTree.innerHTML = ss.i18n._t('LOADING'); + this.itemTree.innerHTML = ss.i18n._t('LOADING', 'Loading...'); this.appendChild(this.itemTree); } }, @@ -186,7 +186,7 @@ TreeDropdownField.prototype = { ajaxExpansion: function() { this.addNodeClass('loading'); var ul = this.treeNodeHolder(); - ul.innerHTML = ss.i18n._t('LOADING'); + ul.innerHTML = ss.i18n._t('LOADING', 'Loading...'); var ajaxURL = this.options.dropdownField.helperURLBase() + 'getsubtree?&SubtreeRootID=' + this.getIdx(); ajaxURL += $('SecurityID') ? '&SecurityID=' + $('SecurityID').value : ''; diff --git a/javascript/UniqueFields.js b/javascript/UniqueFields.js index ea7cec083..3e9bf2d09 100755 --- a/javascript/UniqueFields.js +++ b/javascript/UniqueFields.js @@ -7,7 +7,7 @@ UniqueFormField.prototype = { if( this.restrictedValues[suggested] || suggested == null ) { suggested = this.suggestNewValue(); statusMessage(ss.i18n.sprintf( - ss.i18n._t('UNIQUEFIELD.SUGGESTED'), + ss.i18n._t('UNIQUEFIELD.SUGGESTED', "Changed value to '%s' : %s"), suggested, this.restrictedMessage )); @@ -112,9 +112,9 @@ UniqueRestrictedTextField.prototype = { if( suggested == null || suggested.length == 0 || suggestedValue || suggested.match( this.charRegex ) ) { var message; if( suggested == null ) - message = ss.i18n._t('UNIQUEFIELD.ENTERNEWVALUE'); + message = ss.i18n._t('UNIQUEFIELD.ENTERNEWVALUE', 'You will need to enter a new value for this field'); else if( suggested.length == 0 ) - message = ss.i18n._t('UNIQUEFIELD.CANNOTLEAVEEMPTY'); + message = ss.i18n._t('UNIQUEFIELD.CANNOTLEAVEEMPTY', 'This field cannot be left empty'); else if( suggestedValue ) message = this.restrictedMessage; else @@ -122,7 +122,7 @@ UniqueRestrictedTextField.prototype = { suggested = this.suggestNewValue(); statusMessage(ss.i18n.sprintf( - ss.i18n._t('UNIQUEFIELD.SUGGESTED'), + ss.i18n._t('UNIQUEFIELD.SUGGESTED', "Changed value to '%s' : %s"), suggested, message )); @@ -152,7 +152,7 @@ RestrictedTextField.prototype = { for( var index = 0; index < this.restrictedChars.length; index++ ) { if( lastChar == this.restrictedChars.charAt(index) ) { alert(ss.i18n.sprintf( - ss.i18n._t('RESTRICTEDTEXTFIELD.CHARCANTBEUSED'), + ss.i18n._t('RESTRICTEDTEXTFIELD.CHARCANTBEUSED', "The character '%s' cannot be used in this field"), lastChar )); this.value = this.value.substring( 0, this.value.length - 1 ); diff --git a/javascript/UpdateURL.js b/javascript/UpdateURL.js index 40c03f0da..36ddee2a9 100755 --- a/javascript/UpdateURL.js +++ b/javascript/UpdateURL.js @@ -11,7 +11,7 @@ Behaviour.register({ var newSuggestion = urlSegmentField.suggestNewValue( this.value.toLowerCase() ); var isNew = urlSegmentField.value.indexOf("new") == 0; var confirmMessage = ss.i18n.sprintf( - ss.i18n._t('UPDATEURL.CONFIRM'), + ss.i18n._t('UPDATEURL.CONFIRM', 'Would you like me to change the URL to:\n\n%s/\n\nClick Ok to change the URL, click Cancel to leave it as:\n\n%s'), newSuggestion, urlSegmentField.value ); diff --git a/javascript/Validator.js b/javascript/Validator.js index 7fc061bf2..2dd0b3b9b 100755 --- a/javascript/Validator.js +++ b/javascript/Validator.js @@ -147,7 +147,7 @@ function require(fieldName,cachedError) { fieldlabel = "this field"; } - var errorMessage = ss.i18n.sprintf(ss.i18n._t('VALIDATOR.FIELDREQUIRED'), fieldLabel); + var errorMessage = ss.i18n.sprintf(ss.i18n._t('VALIDATOR.FIELDREQUIRED', 'Please fill out "%s", it is required.'), fieldLabel); if(baseEl.requiredErrorMsg) errorMessage = baseEl.requiredErrorMsg; else if(_CURRENT_FORM.requiredErrorMsg) errorMessage = _CURRENT_FORM.requiredErrorMsg; diff --git a/javascript/i18n.js b/javascript/i18n.js index f8548feb8..6701ae68d 100644 --- a/javascript/i18n.js +++ b/javascript/i18n.js @@ -4,10 +4,10 @@ if(typeof(ss) == 'undefined') ss = {}; * Lightweight clientside i18n implementation. * Caution: Only available after DOM loaded because we need to detect the language * + * For non-i18n stub implementation, see sapphire/javascript/i18nx.js + * * Based on jQuery i18n plugin: 1.0.0 Feb-10-2008 - * @requires jQuery v1.1 or later - * - * Examples at: http://recurser.com/articles/2008/02/21/jquery-i18n-translation-plugin/ + * * Dual licensed under the MIT and GPL licenses: * http://www.opensource.org/licenses/mit-license.php * http://www.gnu.org/licenses/gpl.html @@ -51,8 +51,8 @@ ss.i18n = { * dictionary and returns the translation if one exists. If a translation * is not found, returns the original word * - * @param string entity - * @param string fallbackString + * @param string entity A "long" locale format, e.g. "de_DE" (Required) + * @param string fallbackString (Required) * @param int priority (not used) * @param string context Give translators context for the string * @return string : Translated word diff --git a/javascript/i18nx.js b/javascript/i18nx.js new file mode 100644 index 000000000..c66886a11 --- /dev/null +++ b/javascript/i18nx.js @@ -0,0 +1,40 @@ +if(typeof(ss) == 'undefined') ss = {}; + +/** + * Stub implementation for ss.i18n code. + * Use instead of sapphire/javascript/i18n.js + * if you want to use any SilverStripe javascript + * without internationalization support. + */ +ss.i18n = { + currentLocale: 'en_US', + + defaultLocale: 'en_US', + + _t: function (entity, fallbackString, priority, context) { + return fallbackString; + }, + + sprintf: function(S) { + if (arguments.length == 1) return S; + + var nS = ""; + var tS = S.split("%s"); + + var args = []; + for (var i=1, len = arguments.length; i