1
|
<?php
|
2
|
|
3
|
/**
|
4
|
* @file
|
5
|
* Internationalization (i18n) module - Field handling
|
6
|
*
|
7
|
* For string keys we use:
|
8
|
* - field:[field_name]:[bundle]:property, when it is an instance property (linked to bundle)
|
9
|
* - field:[field_name]:#property..., when it is a field property (that may have multiple values)
|
10
|
*/
|
11
|
|
12
|
/**
|
13
|
* Implements hook_menu().
|
14
|
*/
|
15
|
function i18n_field_menu() {
|
16
|
$items = array();
|
17
|
|
18
|
// Ensure the following is not executed until field_bundles is working and
|
19
|
// tables are updated. Needed to avoid errors on initial installation.
|
20
|
if (!module_exists('field_ui') || defined('MAINTENANCE_MODE')) {
|
21
|
return $items;
|
22
|
}
|
23
|
|
24
|
// Create tabs for all possible bundles. From field_ui_menu().
|
25
|
foreach (entity_get_info() as $entity_type => $entity_info) {
|
26
|
if ($entity_info['fieldable']) {
|
27
|
foreach ($entity_info['bundles'] as $bundle_name => $bundle_info) {
|
28
|
if (isset($bundle_info['admin'])) {
|
29
|
// Extract path information from the bundle.
|
30
|
$path = $bundle_info['admin']['path'];
|
31
|
// Different bundles can appear on the same path (e.g. %node_type and
|
32
|
// %comment_node_type). To allow field_ui_menu_load() to extract the
|
33
|
// actual bundle object from the translated menu router path
|
34
|
// arguments, we need to identify the argument position of the bundle
|
35
|
// name string ('bundle argument') and pass that position to the menu
|
36
|
// loader. The position needs to be casted into a string; otherwise it
|
37
|
// would be replaced with the bundle name string.
|
38
|
if (isset($bundle_info['admin']['bundle argument'])) {
|
39
|
$bundle_arg = $bundle_info['admin']['bundle argument'];
|
40
|
$bundle_pos = (string) $bundle_arg;
|
41
|
}
|
42
|
else {
|
43
|
$bundle_arg = $bundle_name;
|
44
|
$bundle_pos = '0';
|
45
|
}
|
46
|
// This is the position of the %field_ui_menu placeholder in the
|
47
|
// items below.
|
48
|
$field_position = count(explode('/', $path)) + 1;
|
49
|
|
50
|
// Extract access information, providing defaults.
|
51
|
$access = array_intersect_key($bundle_info['admin'], drupal_map_assoc(array('access callback', 'access arguments')));
|
52
|
$access += array(
|
53
|
'access callback' => 'user_access',
|
54
|
'access arguments' => array('administer site configuration'),
|
55
|
);
|
56
|
$items["$path/fields/%field_ui_menu/translate"] = array(
|
57
|
'load arguments' => array($entity_type, $bundle_arg, $bundle_pos, '%map'),
|
58
|
'title' => 'Translate',
|
59
|
'page callback' => 'i18n_field_page_translate',
|
60
|
'page arguments' => array($field_position),
|
61
|
'file' => 'i18n_field.pages.inc',
|
62
|
'type' => MENU_LOCAL_TASK,
|
63
|
) + $access;
|
64
|
|
65
|
$items["$path/fields/%field_ui_menu/translate/%i18n_language"] = array(
|
66
|
'load arguments' => array($entity_type, $bundle_arg, $bundle_pos, '%map'),
|
67
|
'title' => 'Instance',
|
68
|
'page callback' => 'i18n_field_page_translate',
|
69
|
'page arguments' => array($field_position, $field_position + 2),
|
70
|
'file' => 'i18n_field.pages.inc',
|
71
|
'type' => MENU_CALLBACK,
|
72
|
) + $access;
|
73
|
}
|
74
|
}
|
75
|
}
|
76
|
}
|
77
|
return $items;
|
78
|
}
|
79
|
|
80
|
/**
|
81
|
* Implements hook_hook_info().
|
82
|
*/
|
83
|
function i18n_field_hook_info() {
|
84
|
$hooks['i18n_field_info'] = array(
|
85
|
'group' => 'i18n',
|
86
|
);
|
87
|
return $hooks;
|
88
|
}
|
89
|
|
90
|
/**
|
91
|
* Implements hook_field_attach_form().
|
92
|
*
|
93
|
* After the form fields are built. Translate title and description for fields with multiple values.
|
94
|
*/
|
95
|
function i18n_field_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode) {
|
96
|
// Determine the list of instances to iterate on.
|
97
|
list(, , $bundle) = entity_extract_ids($entity_type, $entity);
|
98
|
$instances = field_info_instances($entity_type, $bundle);
|
99
|
foreach ($instances as $field_name => $instance) {
|
100
|
if (isset($form[$field_name])) {
|
101
|
$langcode = $form[$field_name]['#language'];
|
102
|
$field = &$form[$field_name];
|
103
|
// Note: cardinality for unlimited fields is -1
|
104
|
if (isset($field[$langcode]['#cardinality']) && $field[$langcode]['#cardinality'] != 1) {
|
105
|
$translated = i18n_string_object_translate('field_instance', $instance);
|
106
|
if (!empty($field[$langcode]['#title'])) {
|
107
|
$field[$langcode]['#title'] = $translated['label'];
|
108
|
}
|
109
|
if (!empty($field[$langcode]['#description'])) {
|
110
|
$field[$langcode]['#description'] = $translated['description'];
|
111
|
}
|
112
|
}
|
113
|
}
|
114
|
}
|
115
|
}
|
116
|
|
117
|
/**
|
118
|
* Implements hook_field_formatter_info().
|
119
|
*/
|
120
|
function i18n_field_field_formatter_info() {
|
121
|
$types = array();
|
122
|
foreach (i18n_field_type_info() as $type => $info) {
|
123
|
if (!empty($info['translate_options'])) {
|
124
|
$types[] = $type;
|
125
|
}
|
126
|
}
|
127
|
return array(
|
128
|
'i18n_list_default' => array(
|
129
|
'label' => t('Default translated'),
|
130
|
'field types' => $types,
|
131
|
),
|
132
|
);
|
133
|
}
|
134
|
|
135
|
/**
|
136
|
* Implements hook_field_formatter_view().
|
137
|
*/
|
138
|
function i18n_field_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
|
139
|
$element = array();
|
140
|
|
141
|
switch ($display['type']) {
|
142
|
case 'i18n_list_default':
|
143
|
if (($translate = i18n_field_type_info($field['type'], 'translate_options'))) {
|
144
|
$allowed_values = $translate($field);
|
145
|
}
|
146
|
else {
|
147
|
// Defaults to list_default behavior
|
148
|
$allowed_values = list_allowed_values($field);
|
149
|
}
|
150
|
foreach ($items as $delta => $item) {
|
151
|
if (isset($allowed_values[$item['value']])) {
|
152
|
$output = field_filter_xss($allowed_values[$item['value']]);
|
153
|
}
|
154
|
else {
|
155
|
// If no match was found in allowed values, fall back to the key.
|
156
|
$output = field_filter_xss($item['value']);
|
157
|
}
|
158
|
$element[$delta] = array('#markup' => $output);
|
159
|
}
|
160
|
break;
|
161
|
}
|
162
|
|
163
|
return $element;
|
164
|
}
|
165
|
|
166
|
/**
|
167
|
* Implements hook_field_widget_form_alter().
|
168
|
*
|
169
|
* Translate:
|
170
|
* - Title (label)
|
171
|
* - Description (help)
|
172
|
* - Default value
|
173
|
* - List options
|
174
|
*/
|
175
|
function i18n_field_field_widget_form_alter(&$element, &$form_state, $context) {
|
176
|
global $language;
|
177
|
|
178
|
// Don't translate if the widget is being shown on the field edit form.
|
179
|
if ($form_state['build_info']['form_id'] == 'field_ui_field_edit_form') {
|
180
|
return;
|
181
|
}
|
182
|
|
183
|
// Skip if we are missing any of the parameters
|
184
|
if (empty($context['field']) || empty($context['instance']) || empty($context['langcode'])) {
|
185
|
return;
|
186
|
}
|
187
|
$field = $context['field'];
|
188
|
$instance = $context['instance'];
|
189
|
$langcode = $context['langcode'];
|
190
|
|
191
|
// Get the element to alter. Account for inconsistencies in how the element
|
192
|
// is built for different field types.
|
193
|
if (isset($element[0]) && count($element) == 1) {
|
194
|
// Single-value file fields and image fields.
|
195
|
$alter_element = &$element[0];
|
196
|
}
|
197
|
elseif (isset($element['value'])) {
|
198
|
// Number fields. Single-value text fields.
|
199
|
$alter_element = &$element['value'];
|
200
|
}
|
201
|
elseif ($field['type'] == 'entityreference' && isset($element['target_id'])) {
|
202
|
// Entityreference fields using the entityreference_autocomplete widget.
|
203
|
$alter_element = &$element['target_id'];
|
204
|
}
|
205
|
else {
|
206
|
// All other fields.
|
207
|
$alter_element = &$element;
|
208
|
}
|
209
|
|
210
|
// If a subelement has the same title as the parent, translate it instead.
|
211
|
// Allows fields such as email and commerce_price to be translated.
|
212
|
foreach (element_get_visible_children($element) as $key) {
|
213
|
$single_value = ($field['cardinality'] == 1);
|
214
|
$has_title = (isset($element['#title']) && isset($element[$key]['#title']));
|
215
|
if ($single_value && $has_title && $element[$key]['#title'] == $element['#title']) {
|
216
|
$alter_element = &$element[$key];
|
217
|
break;
|
218
|
}
|
219
|
}
|
220
|
|
221
|
// The field language may affect some variables (default) but not others (description will be in current page language)
|
222
|
$i18n_langcode = empty($alter_element['#language']) || $alter_element['#language'] == LANGUAGE_NONE ? $language->language : $alter_element['#language'];
|
223
|
|
224
|
// Translate instance to current page language and set to form_state
|
225
|
// so it will be used for validation messages later.
|
226
|
$instance_current = i18n_string_object_translate('field_instance', $instance);
|
227
|
if (isset($form_state['field'][$instance['field_name']][$langcode]['instance'])) {
|
228
|
$form_state['field'][$instance['field_name']][$langcode]['instance'] = $instance_current;
|
229
|
}
|
230
|
|
231
|
// Translate field title if set and it is the default one.
|
232
|
if (!empty($instance_current['label']) && $instance_current['label'] != $instance['label']) {
|
233
|
if (!empty($alter_element['#title']) && $alter_element['#title'] == check_plain($instance['label'])) {
|
234
|
$alter_element['#title'] = check_plain($instance_current['label']);
|
235
|
}
|
236
|
}
|
237
|
|
238
|
// Translate field description if set and it is the default one.
|
239
|
if (!empty($instance_current['description']) && $instance_current['description'] != $instance['description']) {
|
240
|
if (!empty($alter_element['#description'])) {
|
241
|
// Allow single-value file fields and image fields to have their
|
242
|
// descriptions translated. file_field_widget_form() passes the
|
243
|
// description through theme('file_upload_help'), so i18n_field
|
244
|
// must do the same.
|
245
|
$filefield = in_array($field['type'], array('file', 'image'));
|
246
|
$single_value = ($field['cardinality'] == 1);
|
247
|
$no_default = empty($alter_element['#default_value']['fid']);
|
248
|
if ($filefield && $single_value && $no_default) {
|
249
|
$help_variables = array(
|
250
|
'description' => field_filter_xss($instance['description']),
|
251
|
'upload_validators' => isset($alter_element['#upload_validators']) ? $alter_element['#upload_validators'] : array(),
|
252
|
);
|
253
|
$original_description = theme('file_upload_help', $help_variables);
|
254
|
if ($alter_element['#description'] == $original_description) {
|
255
|
$help_variables = array(
|
256
|
'description' => field_filter_xss($instance_current['description']),
|
257
|
'upload_validators' => isset($alter_element['#upload_validators']) ? $alter_element['#upload_validators'] : array(),
|
258
|
);
|
259
|
$alter_element['#description'] = theme('file_upload_help', $help_variables);
|
260
|
}
|
261
|
}
|
262
|
elseif ($alter_element['#description'] == field_filter_xss($instance['description'])) {
|
263
|
$alter_element['#description'] = field_filter_xss($instance_current['description']);
|
264
|
}
|
265
|
}
|
266
|
}
|
267
|
|
268
|
// Translate list options.
|
269
|
$has_options = (!empty($alter_element['#options']) || $field['type'] == 'list_boolean');
|
270
|
$has_allowed_values = !empty($field['settings']['allowed_values']);
|
271
|
$translate = i18n_field_type_info($field['type'], 'translate_options');
|
272
|
if ($has_options && $has_allowed_values && $translate) {
|
273
|
$alter_element['#options'] = $translate($field, $i18n_langcode);
|
274
|
if (isset($alter_element['#properties']) && !empty($alter_element['#properties']['empty_option'])) {
|
275
|
$label = theme('options_none', array('instance' => $instance, 'option' => $alter_element['#properties']['empty_option']));
|
276
|
$alter_element['#options'] = array('_none' => $label) + $alter_element['#options'];
|
277
|
}
|
278
|
// Translate list_boolean fields using the checkboxes widget.
|
279
|
if (!empty($alter_element['#title']) && $field['type'] == 'list_boolean' && !empty($alter_element['#on_value'])) {
|
280
|
$on_value = $alter_element['#on_value'];
|
281
|
$alter_element['#options'];
|
282
|
$alter_element['#title'] = $alter_element['#options'][$on_value];
|
283
|
// For using label instead of "On value".
|
284
|
if ($instance['widget']['settings']['display_label']) {
|
285
|
$alter_element['#title'] = $instance_current['label'];
|
286
|
}
|
287
|
}
|
288
|
}
|
289
|
|
290
|
// Check for more parameters, skip this part if missing.
|
291
|
if (!isset($context['delta']) || !isset($context['items'])) {
|
292
|
return;
|
293
|
}
|
294
|
$delta = $context['delta'];
|
295
|
$items = $context['items'];
|
296
|
|
297
|
// Translate default value.
|
298
|
$has_default_value = (isset($alter_element['#default_value']) && !empty($instance['default_value'][$delta]['value']));
|
299
|
$storage_has_value = !empty($items[$delta]['value']);
|
300
|
$translate = i18n_field_type_info($field['type'], 'translate_default');
|
301
|
if ($has_default_value && $storage_has_value && $translate) {
|
302
|
// Compare the default value with the value currently in storage.
|
303
|
if ($instance['default_value'][$delta]['value'] === $items[$delta]['value']) {
|
304
|
$alter_element['#default_value'] = $translate($instance, $items[$delta]['value'], $i18n_langcode);
|
305
|
}
|
306
|
}
|
307
|
}
|
308
|
|
309
|
/**
|
310
|
* Implements hook_field_attach_view_alter().
|
311
|
*/
|
312
|
function i18n_field_field_attach_view_alter(&$output, $context) {
|
313
|
foreach (element_children($output) as $field_name) {
|
314
|
$element = &$output[$field_name];
|
315
|
if (!empty($element['#entity_type']) && !empty($element['#field_name']) && !empty($element['#bundle'])) {
|
316
|
$instance = field_info_instance($element['#entity_type'], $element['#field_name'], $element['#bundle']);
|
317
|
|
318
|
// Translate field title if set
|
319
|
if (!empty($instance['label'])) {
|
320
|
$element['#title'] = i18n_field_translate_property($instance, 'label');
|
321
|
}
|
322
|
|
323
|
// Translate field description if set
|
324
|
if (!empty($instance['description'])) {
|
325
|
$element['#description'] = i18n_field_translate_property($instance, 'description');
|
326
|
}
|
327
|
}
|
328
|
}
|
329
|
}
|
330
|
|
331
|
/**
|
332
|
* Implements hook_field_create_field().
|
333
|
*/
|
334
|
function i18n_field_field_create_field($field) {
|
335
|
i18n_field_field_update_strings($field);
|
336
|
}
|
337
|
|
338
|
/**
|
339
|
* Implements hook_field_create_instance().
|
340
|
*/
|
341
|
function i18n_field_field_create_instance($instance) {
|
342
|
i18n_field_instance_update_strings($instance);
|
343
|
}
|
344
|
|
345
|
/**
|
346
|
* Implements hook_field_delete_instance().
|
347
|
*/
|
348
|
function i18n_field_field_delete_instance($instance) {
|
349
|
i18n_string_object_remove('field_instance', $instance);
|
350
|
}
|
351
|
|
352
|
/**
|
353
|
* Implements hook_field_update_instance().
|
354
|
*/
|
355
|
function i18n_field_field_update_instance($instance, $prior_instance) {
|
356
|
i18n_field_instance_update_strings($instance);
|
357
|
}
|
358
|
|
359
|
/**
|
360
|
* Implements hook_field_update_field().
|
361
|
*/
|
362
|
function i18n_field_field_update_field($field) {
|
363
|
i18n_field_field_update_strings($field);
|
364
|
}
|
365
|
|
366
|
/**
|
367
|
* Update field strings
|
368
|
*/
|
369
|
function i18n_field_field_update_strings($field) {
|
370
|
i18n_string_object_update('field', $field);
|
371
|
}
|
372
|
|
373
|
/**
|
374
|
* Update field instance strings
|
375
|
*/
|
376
|
function i18n_field_instance_update_strings($instance) {
|
377
|
i18n_string_object_update('field_instance', $instance);
|
378
|
}
|
379
|
|
380
|
/**
|
381
|
* Returns the array of translated allowed values for a list field.
|
382
|
*
|
383
|
* The strings are not safe for output. Keys and values of the array should be
|
384
|
* sanitized through field_filter_xss() before being displayed.
|
385
|
*
|
386
|
* @param $field
|
387
|
* The field definition.
|
388
|
*
|
389
|
* @return
|
390
|
* The array of allowed values. Keys of the array are the raw stored values
|
391
|
* (number or text), values of the array are the display labels.
|
392
|
*/
|
393
|
function i18n_field_translate_allowed_values($field, $langcode = NULL) {
|
394
|
if (!empty($field['settings']['allowed_values'])) {
|
395
|
return i18n_string_translate(array('field', $field['field_name'], '#allowed_values'), $field['settings']['allowed_values'], array('langcode' => $langcode, 'sanitize' => FALSE));
|
396
|
}
|
397
|
else {
|
398
|
return array();
|
399
|
}
|
400
|
}
|
401
|
|
402
|
/**
|
403
|
* Translate field default.
|
404
|
*/
|
405
|
function i18n_field_translate_default($instance, $value, $langcode = NULL) {
|
406
|
// The default value does not need sanitizing in a text_textfield widget.
|
407
|
$sanitize = !($instance['widget']['type'] == 'text_textfield' && $instance['widget']['module'] == 'text');
|
408
|
return i18n_string_translate(array('field', $instance['field_name'], $instance['bundle'], 'default_value'), $value, array('langcode' => $langcode, 'sanitize' => $sanitize));
|
409
|
}
|
410
|
|
411
|
/**
|
412
|
* Translate field property
|
413
|
*/
|
414
|
function i18n_field_translate_property($instance, $property, $langcode = NULL) {
|
415
|
// For performance reasons, we translate the whole instance once, which is cached.
|
416
|
$instance = i18n_string_object_translate('field_instance', $instance, array('langcode' => $langcode));
|
417
|
return $instance[$property];
|
418
|
}
|
419
|
|
420
|
/**
|
421
|
* Get i18n information for translating fields.
|
422
|
*
|
423
|
* @param $type
|
424
|
* Optional field type.
|
425
|
* @param $property
|
426
|
* Optional property to get from field type.
|
427
|
*
|
428
|
* @return
|
429
|
* - The property for the field if $type and $property set.
|
430
|
* - Array of properties for the field type if only $type is set.
|
431
|
* - Array of translation information for all field types.
|
432
|
*/
|
433
|
function i18n_field_type_info($type = NULL, $property = NULL) {
|
434
|
$info = &drupal_static(__FUNCTION__);
|
435
|
if (!isset($info)) {
|
436
|
$info = module_invoke_all('i18n_field_info');
|
437
|
drupal_alter('i18n_field_info', $info);
|
438
|
}
|
439
|
if ($property) {
|
440
|
return isset($info[$type]) && isset($info[$type][$property]) ? $info[$type][$property] : NULL;
|
441
|
}
|
442
|
elseif ($type) {
|
443
|
return isset($info[$type]) ? $info[$type] : array();
|
444
|
}
|
445
|
else {
|
446
|
return $info;
|
447
|
}
|
448
|
}
|