Commit b3e112fe authored by catch's avatar catch

Issue #2182265 by Sweetchuck, nod_: Javascript file translations are several...

Issue #2182265 by Sweetchuck, nod_: Javascript file translations are several bugs away from working at all.
parent adedd244
......@@ -192,9 +192,9 @@ if (window.jQuery) {
/**
* Replace placeholders with sanitized values in a string.
*
* @param str
* @param {String} str
* A string with placeholders.
* @param args
* @param {Object} args
* An object of replacements pairs to make. Incidences of any key in this
* array are replaced with the corresponding value. Based on the first
* character of the key, the value is escaped and/or themed:
......@@ -203,6 +203,9 @@ if (window.jQuery) {
* - %variable: escape text and theme as a placeholder for user-submitted
* content (checkPlain + Drupal.theme('placeholder'))
*
* @return {String}
* Returns the replaced string.
*
* @see Drupal.t()
* @ingroup sanitization
*/
......@@ -223,10 +226,61 @@ if (window.jQuery) {
args[key] = Drupal.theme('placeholder', args[key]);
break;
}
str = str.replace(key, args[key]);
}
}
return str;
return Drupal.stringReplace(str, args, null);
};
/**
* Replace substring.
*
* The longest keys will be tried first. Once a substring has been replaced,
* its new value will not be searched again.
*
* @param {String} str
* A string with placeholders.
* @param {Object} args
* Key-value pairs.
* @param {Array|null} keys
* Array of keys from the "args". Internal use only.
*
* @return {String}
* Returns the replaced string.
*/
Drupal.stringReplace = function (str, args, keys) {
if (str.length === 0) {
return str;
}
// If the array of keys is not passed then collect the keys from the args.
if (!Array.isArray(keys)) {
keys = [];
for (var k in args) {
if (args.hasOwnProperty(k)) {
keys.push(k);
}
}
// Order the keys by the character length. The shortest one is the first.
keys.sort(function (a, b) { return a.length - b.length; });
}
if (keys.length === 0) {
return str;
}
// Take next longest one from the end.
var key = keys.pop();
var fragments = str.split(key);
if (keys.length) {
for (var i = 0; i < fragments.length; i++) {
fragments[i] = Drupal.stringReplace(fragments[i], args, keys);
}
}
return fragments.join(args[key]);
};
/**
......@@ -273,49 +327,48 @@ if (window.jQuery) {
/**
* Format a string containing a count of items.
*
* This function ensures that the string is pluralized correctly. Since Drupal.t() is
* called by this function, make sure not to pass already-localized strings to it.
* This function ensures that the string is pluralized correctly. Since
* Drupal.t() is called by this function, make sure not to pass
* already-localized strings to it.
*
* See the documentation of the server-side format_plural() function for further details.
* See the documentation of the server-side format_plural() function for
* further details.
*
* @param count
* @param {Number} count
* The item count to display.
* @param singular
* @param {String} singular
* The string for the singular case. Please make sure it is clear this is
* singular, to ease translation (e.g. use "1 new comment" instead of "1 new").
* Do not use @count in the singular string.
* @param plural
* The string for the plural case. Please make sure it is clear this is plural,
* to ease translation. Use @count in place of the item count, as in "@count
* new comments".
* @param args
* singular, to ease translation (e.g. use "1 new comment" instead of "1
* new"). Do not use @count in the singular string.
* @param {String} plural
* The string for the plural case. Please make sure it is clear this is
* plural, to ease translation. Use @count in place of the item count, as in
* "@count new comments".
* @param {Object} args
* An object of replacements pairs to make after translation. Incidences
* of any key in this array are replaced with the corresponding value.
* See Drupal.formatString().
* Note that you do not need to include @count in this array.
* This replacement is done automatically for the plural case.
* @param options
* @param {Object} options
* The options to pass to the Drupal.t() function.
* @return
*
* @return {String}
* A translated string.
*/
Drupal.formatPlural = function (count, singular, plural, args, options) {
args = args || {};
args['@count'] = count;
var pluralDelimiter = Drupal.locale.pluralDelimiter;
// Determine the index of the plural form.
var index = Drupal.locale.pluralFormula ? Drupal.locale.pluralFormula(args['@count']) : ((args['@count'] === 1) ? 0 : 1);
var translations = Drupal
.t(singular + pluralDelimiter + plural, args, options)
.split(pluralDelimiter);
if (index === 0) {
return Drupal.t(singular, args, options);
}
else if (index === 1) {
return Drupal.t(plural, args, options);
}
else {
args['@count[' + index + ']'] = args['@count'];
delete args['@count'];
return Drupal.t(plural.replace('@count', '@count[' + index + ']'), args, options);
}
return translations[index];
};
/**
......
......@@ -31,7 +31,6 @@ public static function getInfo() {
}
function testFileParsing() {
$filename = drupal_get_path('module', 'locale') . '/tests/locale_test.js';
// Parse the file to look for source strings.
......@@ -44,52 +43,46 @@ function testFileParsing() {
'type' => 'javascript',
'name' => $filename,
));
$source_strings = array();
foreach ($strings as $string) {
$source_strings[$string->source] = $string->context;
}
$etx = LOCALE_PLURAL_DELIMITER;
// List of all strings that should be in the file.
$test_strings = array(
"Standard Call t" => '',
"Whitespace Call t" => '',
'Standard Call t' => '',
'Whitespace Call t' => '',
"Single Quote t" => '',
'Single Quote t' => '',
"Single Quote \\'Escaped\\' t" => '',
"Single Quote Concat strings t" => '',
'Single Quote Concat strings t' => '',
"Double Quote t" => '',
'Double Quote t' => '',
"Double Quote \\\"Escaped\\\" t" => '',
"Double Quote Concat strings t" => '',
"Context !key Args t" => "Context string",
"Context Unquoted t" => "Context string unquoted",
"Context Single Quoted t" => "Context string single quoted",
"Context Double Quoted t" => "Context string double quoted",
"Standard Call plural" => '',
"Standard Call @count plural" => '',
"Whitespace Call plural" => '',
"Whitespace Call @count plural" => '',
"Single Quote plural" => '',
"Single Quote @count plural" => '',
"Single Quote \\'Escaped\\' plural" => '',
"Single Quote \\'Escaped\\' @count plural" => '',
"Double Quote plural" => '',
"Double Quote @count plural" => '',
"Double Quote \\\"Escaped\\\" plural" => '',
"Double Quote \\\"Escaped\\\" @count plural" => '',
"Context !key Args plural" => "Context string",
"Context !key Args @count plural" => "Context string",
"Context Unquoted plural" => "Context string unquoted",
"Context Unquoted @count plural" => "Context string unquoted",
"Context Single Quoted plural" => "Context string single quoted",
"Context Single Quoted @count plural" => "Context string single quoted",
"Context Double Quoted plural" => "Context string double quoted",
"Context Double Quoted @count plural" => "Context string double quoted",
'Double Quote Concat strings t' => '',
'Context !key Args t' => 'Context string',
'Context Unquoted t' => 'Context string unquoted',
'Context Single Quoted t' => 'Context string single quoted',
'Context Double Quoted t' => 'Context string double quoted',
"Standard Call plural{$etx}Standard Call @count plural" => '',
"Whitespace Call plural{$etx}Whitespace Call @count plural" => '',
"Single Quote plural{$etx}Single Quote @count plural" => '',
"Single Quote \\'Escaped\\' plural{$etx}Single Quote \\'Escaped\\' @count plural" => '',
"Double Quote plural{$etx}Double Quote @count plural" => '',
"Double Quote \\\"Escaped\\\" plural{$etx}Double Quote \\\"Escaped\\\" @count plural" => '',
"Context !key Args plural{$etx}Context !key Args @count plural" => 'Context string',
"Context Unquoted plural{$etx}Context Unquoted @count plural" => 'Context string unquoted',
"Context Single Quoted plural{$etx}Context Single Quoted @count plural" => 'Context string single quoted',
"Context Double Quoted plural{$etx}Context Double Quoted @count plural" => 'Context string double quoted',
);
// Assert that all strings were found properly.
......@@ -97,12 +90,13 @@ function testFileParsing() {
$args = array('%source' => $str, '%context' => $context);
// Make sure that the string was found in the file.
$this->assertTrue(isset($source_strings[$str]), String::format("Found source string: %source", $args));
$this->assertTrue(isset($source_strings[$str]), String::format('Found source string: %source', $args));
// Make sure that the proper context was matched.
$this->assertTrue(isset($source_strings[$str]) && $source_strings[$str] === $context, strlen($context) > 0 ? String::format("Context for %source is %context", $args) : String::format("Context for %source is blank", $args));
$message = $context ? String::format('Context for %source is %context', $args) : String::format('Context for %source is blank', $args);
$this->assertTrue(isset($source_strings[$str]) && $source_strings[$str] === $context, $message);
}
$this->assertEqual(count($source_strings), count($test_strings), "Found correct number of source strings.");
$this->assertEqual(count($source_strings), count($test_strings), 'Found correct number of source strings.');
}
}
......@@ -10,6 +10,7 @@
* object files are supported.
*/
use Drupal\Component\Utility\Json;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Language\Language;
use Drupal\Component\Utility\Crypt;
......@@ -644,7 +645,7 @@ function locale_js_translate(array $files = array()) {
}
// Add the translation JavaScript file to the page.
$locale_javascripts = \Drupal::state()->get('translation.javascript') ?: array();
$locale_javascripts = \Drupal::state()->get('locale.translation.javascript') ?: array();
$translation_file = NULL;
if (!empty($files) && !empty($locale_javascripts[$language_interface->id])) {
// Add the translation JavaScript file to the page.
......@@ -1167,6 +1168,19 @@ function _locale_refresh_configuration(array $langcodes, array $lids) {
}
}
/**
* Removes the quotes and string concatenations from the string.
*
* @param string $string
* Single or double quoted strings, optionally concatenated by plus (+) sign.
*
* @return string
* String with leading and trailing quotes removed.
*/
function _locale_strip_quotes($string) {
return implode('', preg_split('~(?<!\\\\)[\'"]\s*\+\s*[\'"]~s', substr($string, 1, -1)));
}
/**
* Parses a JavaScript file, extracts strings wrapped in Drupal.t() and
* Drupal.formatPlural() and inserts them into the database.
......@@ -1241,43 +1255,28 @@ function _locale_parse_js_file($filepath) {
// Add strings from Drupal.t().
foreach ($t_matches[1] as $key => $string) {
$matches[] = array(
'string' => $string,
'context' => $t_matches[2][$key],
'source' => _locale_strip_quotes($string),
'context' => _locale_strip_quotes($t_matches[2][$key]),
);
}
// Add string from Drupal.formatPlural().
foreach ($plural_matches[1] as $key => $string) {
$matches[] = array(
'string' => $string,
'context' => $plural_matches[3][$key],
'source' => _locale_strip_quotes($string) . LOCALE_PLURAL_DELIMITER . _locale_strip_quotes($plural_matches[2][$key]),
'context' => _locale_strip_quotes($plural_matches[3][$key]),
);
// If there is also a plural version of this string, add it to the strings array.
if (isset($plural_matches[2][$key])) {
$matches[] = array(
'string' => $plural_matches[2][$key],
'context' => $plural_matches[3][$key],
);
}
}
// Loop through all matches and process them.
foreach ($matches as $match) {
// Remove the quotes and string concatenations from the string and context.
$string = implode('', preg_split('~(?<!\\\\)[\'"]\s*\+\s*[\'"]~s', substr($match['string'], 1, -1)));
$context = implode('', preg_split('~(?<!\\\\)[\'"]\s*\+\s*[\'"]~s', substr($match['context'], 1, -1)));
$source = \Drupal::service('locale.storage')->findString(array('source' => $string, 'context' => $context));
$source = \Drupal::service('locale.storage')->findString($match);
if (!$source) {
// We don't have the source string yet, thus we insert it into the database.
$source = \Drupal::service('locale.storage')->createString(array(
'source' => $string,
'context' => $context,
));
$source = \Drupal::service('locale.storage')->createString($match);
}
// Besides adding the location this will tag it for current version.
$source->addLocation('javascript', $filepath);
$source->save();
......@@ -1322,6 +1321,9 @@ function _locale_invalidate_js($langcode = NULL) {
*
* @param $langcode
* The language, the translation file should be (re)created for.
*
* @return bool
* TRUE if translation file exists, FALSE otherwise.
*/
function _locale_rebuild_js($langcode = NULL) {
$config = \Drupal::config('locale.settings');
......@@ -1350,14 +1352,18 @@ function _locale_rebuild_js($langcode = NULL) {
$data_hash = NULL;
$data = $status = '';
if (!empty($translations)) {
$data = array(
'pluralDelimiter: ' . Json::encode(LOCALE_PLURAL_DELIMITER),
);
$data = "Drupal.locale = { ";
$locale_plurals = \Drupal::state()->get('locale.translation.plurals') ?: array();
if (!empty($locale_plurals[$language->id])) {
$data .= "'pluralFormula': function (\$n) { return Number({$locale_plurals[$language->id]['formula']}); }, ";
if (!empty($locale_plurals[$language->id]['formula'])) {
$data[] = "pluralFormula: function (\$n) { return Number({$locale_plurals[$language->id]['formula']}); }";
}
$data .= "'strings': " . drupal_json_encode($translations) . " };";
$data[] = 'strings: ' . Json::encode($translations);
$data = 'Drupal.locale = { ' . implode(', ', $data) . ' };';
$data_hash = Crypt::hashBase64($data);
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment