1
|
<?php
|
2
|
|
3
|
/**
|
4
|
* @file
|
5
|
* Internationalization (i18n) package - translatable strings reusable admin UI.
|
6
|
*
|
7
|
* @author Jose A. Reyero, 2007
|
8
|
*/
|
9
|
|
10
|
// Load locale libraries
|
11
|
include_once DRUPAL_ROOT . '/includes/locale.inc';
|
12
|
include_once drupal_get_path('module', 'locale') . '/locale.admin.inc';
|
13
|
|
14
|
/**
|
15
|
* Generate translate page from object.
|
16
|
*
|
17
|
* @param string $object_type
|
18
|
* Obejct type as declared in hook_i18n_object_info().
|
19
|
* @param object $object_value
|
20
|
* Drupal object to translate.
|
21
|
* @param object $language
|
22
|
* Optional language object.
|
23
|
*/
|
24
|
function i18n_string_translate_page_object($object_type, $object_value, $language = NULL) {
|
25
|
// For backwards compatibility, ensure parameter is a language object
|
26
|
$language = i18n_language_object($language);
|
27
|
$langcode = $language ? $language->language : NULL;
|
28
|
// Get base keys for all these strings. Object key may be multiple like for blocks (module, delta)
|
29
|
$object = i18n_object($object_type, $object_value);
|
30
|
$strings = $object->get_strings(array('empty' => TRUE));
|
31
|
|
32
|
// If no localizable strings, print message and fail gracefully.
|
33
|
// Possibly this object comes from some other contrib module.
|
34
|
// See http://drupal.org/node/1889878
|
35
|
if (!$strings) {
|
36
|
return t('This object has no strings available for translation.');
|
37
|
}
|
38
|
|
39
|
if (empty($langcode)) {
|
40
|
drupal_set_title(t('Translate !name', array('!name' => i18n_object_info($object_type, 'title'))));
|
41
|
return i18n_string_translate_page_overview($object, $strings);
|
42
|
}
|
43
|
else {
|
44
|
drupal_set_title(t('Translate to !language', array('!language' => i18n_language_name($langcode))));
|
45
|
return drupal_get_form('i18n_string_translate_page_form', $strings, $langcode);
|
46
|
}
|
47
|
}
|
48
|
|
49
|
/**
|
50
|
* Provide a core translation module like overview page for this object.
|
51
|
*/
|
52
|
function i18n_string_translate_page_overview($object, $strings) {
|
53
|
$build['i18n_overview'] = drupal_get_form('i18n_string_translate_page_overview_form', $object, $strings);
|
54
|
return $build;
|
55
|
}
|
56
|
|
57
|
/**
|
58
|
* Provide a core translation module like overview page for this object.
|
59
|
*/
|
60
|
function i18n_string_translate_page_overview_form($form, &$form_state, $object, $strings) {
|
61
|
// Set the default item key, assume it's the first.
|
62
|
$item_title = reset($strings);
|
63
|
$header = array(
|
64
|
'language' => t('Language'),
|
65
|
'title' => t('Title'),
|
66
|
'status' => t('Status'),
|
67
|
'operations' => t('Operations')
|
68
|
);
|
69
|
$source_language = variable_get_value('i18n_string_source_language');
|
70
|
$rows = array();
|
71
|
|
72
|
foreach (language_list() as $langcode => $language) {
|
73
|
if ($langcode == $source_language) {
|
74
|
$items = array(
|
75
|
'language' => check_plain($language->name) . ' ' . t('(source)'),
|
76
|
'title' => check_plain($item_title->get_string()),
|
77
|
'status' => t('original'),
|
78
|
'operations' => l(t('edit'), $object->get_edit_path()),
|
79
|
);
|
80
|
}
|
81
|
else {
|
82
|
// Try to figure out if this item has any of its properties translated.
|
83
|
$translated = FALSE;
|
84
|
foreach ($strings as $i18nstring) {
|
85
|
if ($i18nstring->get_translation($langcode)) {
|
86
|
$translated = TRUE;
|
87
|
break;
|
88
|
}
|
89
|
}
|
90
|
// Translate the item that was requested to be displayed as title.
|
91
|
$items = array(
|
92
|
'language' => check_plain($language->name),
|
93
|
'title' => $item_title->format_translation($langcode, array('sanitize default' => TRUE)),
|
94
|
'status' => $translated ? t('translated') : t('not translated'),
|
95
|
'operations' => l(t('translate'), $object->get_translate_path($langcode), array('query' => drupal_get_destination())),
|
96
|
);
|
97
|
}
|
98
|
foreach ($items as $key => $markup) {
|
99
|
$rows[$langcode][$key] = $markup;
|
100
|
//$form['#rows'][$langcode][$key]['#markup'] = $markup;
|
101
|
}
|
102
|
}
|
103
|
// Build a form so it can be altered later, with all this information.
|
104
|
$form['object'] = array('#type' => 'value', '#value' => $object);
|
105
|
$form['source_language'] = array('#type' => 'value', '#value' => $source_language);
|
106
|
$form['languages'] = array(
|
107
|
'#header' => $header,
|
108
|
'#rows' => $rows,
|
109
|
'#theme' => 'table',
|
110
|
);
|
111
|
return $form;
|
112
|
}
|
113
|
|
114
|
/**
|
115
|
* Form builder callback for in-place string translation.
|
116
|
*
|
117
|
* @param $strings
|
118
|
* Array of strings indexed by string name (may be indexed by group key too if $groups is present)
|
119
|
* @param $langcode
|
120
|
* Language code to translate to.
|
121
|
* @param $groups
|
122
|
* Optional groups to provide some string grouping. Array with group key and title pairs.
|
123
|
*/
|
124
|
function i18n_string_translate_page_form($form, &$form_state, $strings, $langcode, $groups = NULL) {
|
125
|
$form = i18n_string_translate_page_form_base($form, $langcode);
|
126
|
if ($groups) {
|
127
|
// I we've got groups, string list is grouped by group key.
|
128
|
$form['string_groups'] = array('#type' => 'value', '#value' => $strings);
|
129
|
foreach ($groups as $key => $title) {
|
130
|
$form['display'] = array(
|
131
|
'#type' => 'vertical_tabs',
|
132
|
);
|
133
|
$form['strings'][$key] = array(
|
134
|
'#group' => 'display',
|
135
|
'#title' => $title,
|
136
|
'#type' => 'fieldset',
|
137
|
) + i18n_string_translate_page_form_strings($strings[$key], $langcode);
|
138
|
}
|
139
|
}
|
140
|
else {
|
141
|
// We add a single group with key 'all', but no tabs.
|
142
|
$form['string_groups'] = array('#type' => 'value', '#value' => array('all' => $strings));
|
143
|
$form['strings']['all'] = i18n_string_translate_page_form_strings($strings, $langcode);
|
144
|
}
|
145
|
return $form;
|
146
|
}
|
147
|
|
148
|
/**
|
149
|
* Create base form for string translation
|
150
|
*/
|
151
|
function i18n_string_translate_page_form_base($form, $langcode, $redirect = NULL) {
|
152
|
$form['langcode'] = array(
|
153
|
'#type' => 'value',
|
154
|
'#value' => $langcode,
|
155
|
);
|
156
|
$form['submit'] = array(
|
157
|
'#type' => 'submit',
|
158
|
'#value' => t('Save translation'),
|
159
|
'#weight' => 10,
|
160
|
);
|
161
|
if ($redirect) {
|
162
|
$form['#redirect'] = array(
|
163
|
$redirect,
|
164
|
);
|
165
|
}
|
166
|
// Add explicit validate and submit hooks so this can be used from inside any form.
|
167
|
$form['#submit'] = array('i18n_string_translate_page_form_submit');
|
168
|
return $form;
|
169
|
}
|
170
|
|
171
|
/**
|
172
|
* Create field elements for strings
|
173
|
*/
|
174
|
function i18n_string_translate_page_form_strings($strings, $langcode) {
|
175
|
$formats = filter_formats();
|
176
|
foreach ($strings as $item) {
|
177
|
// We may have a source or not. Load it, our string may get the format from it.
|
178
|
$source = $item->get_source();
|
179
|
$format_id = $source ? $source->format : $item->format;
|
180
|
$description = '';
|
181
|
// Check permissions to translate this string, depends on format, etc..
|
182
|
if ($message = $item->check_translate_access()) {
|
183
|
// We'll display a disabled element with the reason it cannot be translated.
|
184
|
$disabled = TRUE;
|
185
|
$description = $message;
|
186
|
}
|
187
|
else {
|
188
|
$disabled = FALSE;
|
189
|
$description = '';
|
190
|
// If we don't have a source and it can be translated, we create it.
|
191
|
if (!$source) {
|
192
|
// Enable messages just as a reminder these strings are not being updated properly.
|
193
|
$status = $item->update(array('messages' => TRUE));
|
194
|
if ($status === FALSE || $status === SAVED_DELETED) {
|
195
|
// We don't have a source string so nothing to translate here
|
196
|
$disabled = TRUE;
|
197
|
}
|
198
|
else {
|
199
|
$source = $item->get_source();
|
200
|
}
|
201
|
}
|
202
|
}
|
203
|
|
204
|
$default_value = $item->format_translation($langcode, array('langcode' => $langcode, 'sanitize' => FALSE, 'debug' => FALSE));
|
205
|
$form[$item->get_name()] = array(
|
206
|
'#title' => $item->get_title(),
|
207
|
'#type' => 'textarea',
|
208
|
'#default_value' => $default_value,
|
209
|
'#disabled' => $disabled,
|
210
|
'#description' => $description . _i18n_string_translate_format_help($format_id),
|
211
|
//'#i18n_string_format' => $source ? $source->format : 0,
|
212
|
// If disabled, provide smaller textarea (that can be expanded anyway).
|
213
|
'#rows' => $disabled ? 1 : min(ceil(str_word_count($default_value) / 12), 10),
|
214
|
// Change the parent for disabled strings so we don't get empty values later
|
215
|
'#parents' => array($disabled ? 'disabled_strings': 'strings', $item->get_name()),
|
216
|
);
|
217
|
}
|
218
|
return $form;
|
219
|
}
|
220
|
|
221
|
/**
|
222
|
* Form submission callback for in-place string translation.
|
223
|
*/
|
224
|
function i18n_string_translate_page_form_submit($form, &$form_state) {
|
225
|
$count = $success = 0;
|
226
|
foreach ($form_state['values']['strings'] as $name => $value) {
|
227
|
$count++;
|
228
|
list($textgroup, $context) = i18n_string_context(explode(':', $name));
|
229
|
$result = i18n_string_textgroup($textgroup)->update_translation($context, $form_state['values']['langcode'], $value);
|
230
|
$success += ($result ? 1 : 0);
|
231
|
}
|
232
|
if ($success) {
|
233
|
drupal_set_message(format_plural($success, 'A translation was saved successfully.', '@count translations were saved successfully.'));
|
234
|
}
|
235
|
if ($error = $count - $success) {
|
236
|
drupal_set_message(format_plural($error, 'A translation could not be saved.', '@count translations could not be saved.'), 'warning');
|
237
|
}
|
238
|
if (isset($form['#redirect'])) {
|
239
|
$form_state['redirect'] = $form['#redirect'];
|
240
|
}
|
241
|
}
|
242
|
|
243
|
/**
|
244
|
* Menu callback. Saves a string translation coming as POST data.
|
245
|
*/
|
246
|
function i18n_string_l10n_client_save_string() {
|
247
|
global $user, $language;
|
248
|
|
249
|
if (user_access('use on-page translation')) {
|
250
|
$textgroup = !empty($_POST['textgroup']) ? $_POST['textgroup'] : 'default';
|
251
|
// Other textgroups will be handled by l10n_client module
|
252
|
if (!i18n_string_group_info($textgroup)) {
|
253
|
return l10n_client_save_string();
|
254
|
}
|
255
|
elseif (isset($_POST['source']) && isset($_POST['target']) && !empty($_POST['context']) && !empty($_POST['form_token']) && drupal_valid_token($_POST['form_token'], 'l10n_client_form')) {
|
256
|
$name = $textgroup . ':' . $_POST['context'];
|
257
|
if ($i18nstring = i18n_string_get_source($name)) {
|
258
|
// Since this is not a real form, we double check access permissions here too.
|
259
|
if ($error = $i18nstring->check_translate_access()) {
|
260
|
$message = theme('l10n_client_message', array('message' => t('Not saved due to: !reason', array('!reason' => $error)), 'level' => WATCHDOG_WARNING));
|
261
|
}
|
262
|
else {
|
263
|
$result = i18n_string_translation_update($name, $_POST['target'], $language->language, $_POST['source']);
|
264
|
if ($result) {
|
265
|
$message = theme('l10n_client_message', array('message' => t('Translation saved locally for user defined string.'), 'level' => WATCHDOG_INFO));
|
266
|
}
|
267
|
elseif ($result === FALSE) {
|
268
|
$message = theme('l10n_client_message', array('message' => t('Not saved due to insufficient permissions.')));
|
269
|
}
|
270
|
else {
|
271
|
$message = theme('l10n_client_message', array('message' => t('Not saved due to unknown reason.')));
|
272
|
}
|
273
|
}
|
274
|
}
|
275
|
else {
|
276
|
$message = theme('l10n_client_message', array('message' => t('Not saved due to source string missing.')));
|
277
|
}
|
278
|
}
|
279
|
else {
|
280
|
$message = theme('l10n_client_message', array('message' => t('Not saved due to missing form values.')));
|
281
|
}
|
282
|
drupal_json_output($message);
|
283
|
exit;
|
284
|
}
|
285
|
}
|
286
|
|
287
|
|
288
|
/**
|
289
|
* User interface for string editing.
|
290
|
*/
|
291
|
function i18n_string_locale_translate_edit_form($form, &$form_state, $lid) {
|
292
|
// Fetch source string, if possible.
|
293
|
$source = db_query('SELECT source, context, textgroup, location FROM {locales_source} WHERE lid = :lid', array(':lid' => $lid))->fetchObject();
|
294
|
if (!$source) {
|
295
|
drupal_set_message(t('String not found.'), 'error');
|
296
|
drupal_goto('admin/config/regional/translate/translate');
|
297
|
}
|
298
|
|
299
|
// Add original text to the top and some values for form altering.
|
300
|
$form['original'] = array(
|
301
|
'#type' => 'item',
|
302
|
'#title' => t('Original text'),
|
303
|
'#markup' => check_plain(wordwrap($source->source, 0)),
|
304
|
);
|
305
|
if (!empty($source->context)) {
|
306
|
$form['context'] = array(
|
307
|
'#type' => 'item',
|
308
|
'#title' => t('Context'),
|
309
|
'#markup' => check_plain($source->context),
|
310
|
);
|
311
|
}
|
312
|
$form['lid'] = array(
|
313
|
'#type' => 'value',
|
314
|
'#value' => $lid
|
315
|
);
|
316
|
$form['textgroup'] = array(
|
317
|
'#type' => 'value',
|
318
|
'#value' => $source->textgroup,
|
319
|
);
|
320
|
$form['location'] = array(
|
321
|
'#type' => 'value',
|
322
|
'#value' => $source->location
|
323
|
);
|
324
|
|
325
|
// Include default form controls with empty values for all languages.
|
326
|
// This ensures that the languages are always in the same order in forms.
|
327
|
$languages = language_list();
|
328
|
|
329
|
// We don't need the default language value, that value is in $source.
|
330
|
$omit = $source->textgroup == 'default' ? 'en' : i18n_string_source_language();
|
331
|
unset($languages[($omit)]);
|
332
|
$form['translations'] = array('#tree' => TRUE);
|
333
|
// Approximate the number of rows to use in the default textarea.
|
334
|
$rows = min(ceil(str_word_count($source->source) / 12), 10);
|
335
|
foreach ($languages as $langcode => $language) {
|
336
|
$form['translations'][$langcode] = array(
|
337
|
'#type' => 'textarea',
|
338
|
'#title' => t($language->name),
|
339
|
'#rows' => $rows,
|
340
|
'#default_value' => '',
|
341
|
);
|
342
|
}
|
343
|
|
344
|
// Fetch translations and fill in default values in the form.
|
345
|
$result = db_query("SELECT DISTINCT translation, language FROM {locales_target} WHERE lid = :lid AND language <> :omit", array(':lid' => $lid, ':omit' => $omit));
|
346
|
foreach ($result as $translation) {
|
347
|
$form['translations'][$translation->language]['#default_value'] = $translation->translation;
|
348
|
}
|
349
|
|
350
|
$form['actions'] = array('#type' => 'actions');
|
351
|
$form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save translations'));
|
352
|
|
353
|
// Restrict filter permissions and handle validation and submission for i18n strings.
|
354
|
if (i18n_string_group_info($source->textgroup)) {
|
355
|
if ($i18nstring = i18n_string_get_by_lid($form['lid']['#value'])) {
|
356
|
$form['i18n_string'] = array('#type' => 'value', '#value' => $i18nstring);
|
357
|
if ($message = $i18nstring->check_translate_access()) {
|
358
|
drupal_set_message($message);
|
359
|
$disabled = TRUE;
|
360
|
}
|
361
|
// Add format help anyway, though the form may be disabled.
|
362
|
$form['translations']['format_help']['#markup'] = _i18n_string_translate_format_help($i18nstring->format);
|
363
|
}
|
364
|
else {
|
365
|
drupal_set_message(t('Source string not found.'), 'warning');
|
366
|
$disabled = TRUE;
|
367
|
}
|
368
|
if (!empty($disabled)) {
|
369
|
// Disable all form elements
|
370
|
$form['submit']['#disabled'] = TRUE;
|
371
|
foreach (element_children($form['translations']) as $langcode) {
|
372
|
$form['translations'][$langcode]['#disabled'] = TRUE;
|
373
|
}
|
374
|
}
|
375
|
}
|
376
|
return $form;
|
377
|
}
|
378
|
|
379
|
/**
|
380
|
* Process string editing form validations.
|
381
|
*
|
382
|
* If it is an allowed format, skip default validation, the text will be filtered later
|
383
|
*/
|
384
|
function i18n_string_locale_translate_edit_form_validate($form, &$form_state) {
|
385
|
if (empty($form_state['values']['i18n_string'])) {
|
386
|
// If not i18n string use regular locale validation.
|
387
|
$copy_state = $form_state;
|
388
|
locale_translate_edit_form_validate($form, $copy_state);
|
389
|
}
|
390
|
}
|
391
|
|
392
|
/**
|
393
|
* Process string editing form submissions.
|
394
|
*
|
395
|
* Mark translations as current.
|
396
|
*/
|
397
|
function i18n_string_locale_translate_edit_form_submit($form, &$form_state) {
|
398
|
// Invoke locale submission.
|
399
|
locale_translate_edit_form_submit($form, $form_state);
|
400
|
$lid = $form_state['values']['lid'];
|
401
|
if ($i18n_string_object = i18n_string_get_by_lid($lid)) {
|
402
|
$i18n_string_object->cache_reset();
|
403
|
}
|
404
|
foreach ($form_state['values']['translations'] as $key => $value) {
|
405
|
if (!empty($value)) {
|
406
|
// An update has been made, so we assume the translation is now current.
|
407
|
db_update('locales_target')
|
408
|
->fields(array('i18n_status' => I18N_STRING_STATUS_CURRENT))
|
409
|
->condition('lid', $lid)
|
410
|
->condition('language', $key)
|
411
|
->execute();
|
412
|
}
|
413
|
}
|
414
|
}
|
415
|
|
416
|
/**
|
417
|
* Help for text format.
|
418
|
*/
|
419
|
function _i18n_string_translate_format_help($format_id) {
|
420
|
$output = '';
|
421
|
if ($format = filter_format_load($format_id)) {
|
422
|
$title = t('Text format: @name', array('@name' => $format->name));
|
423
|
$tips = theme('filter_tips', array('tips' => _filter_tips($format_id, FALSE)));
|
424
|
}
|
425
|
elseif ($format_id == I18N_STRING_FILTER_XSS) {
|
426
|
$title = t('Standard XSS filter.');
|
427
|
$allowed_html = '<a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>';
|
428
|
$tips[] = t('Allowed HTML tags: @tags', array('@tags' => $allowed_html));
|
429
|
}
|
430
|
elseif ($format_id == I18N_STRING_FILTER_XSS_ADMIN) {
|
431
|
$title = t('Administration XSS filter.');
|
432
|
$tips[] = t('It will allow most HTML tags but not scripts nor styles.');
|
433
|
}
|
434
|
elseif ($format_id) {
|
435
|
$title = t('Unknown filter: @name', array('@name' => $format_id));
|
436
|
}
|
437
|
|
438
|
if (!empty($title)) {
|
439
|
$output .= '<h5>' . $title . '</h5>';
|
440
|
}
|
441
|
if (!empty($tips)) {
|
442
|
$output .= is_array($tips) ? theme('item_list', array('items' => $tips)) : $tips;
|
443
|
}
|
444
|
return $output;
|
445
|
}
|