locale.inc

  1. drupal
    1. 4.6 includes/locale.inc
    2. 4.7 includes/locale.inc
    3. 5 includes/locale.inc
    4. 6 includes/locale.inc
    5. 7 includes/locale.inc

Admin-related functions for locale.module.

Functions & methods

NameDescription
locale_add_language_form_submitProcess the language addition form submission.
locale_add_language_form_validateValidate the language addition form.
theme_locale_admin_manage_screenTheme the locale admin manager form.
_locale_add_languageHelper function to add a language
_locale_admin_export_screenUser interface for the translation export screen
_locale_admin_import_screenUser interface for the translation import screen.
_locale_admin_import_submitProcess the locale import form submission.
_locale_admin_manage_add_screenUser interface for the language addition screen.
_locale_admin_manage_screenUser interface for the language management screen.
_locale_admin_manage_screen_submitProcess locale admin manager form submissions.
_locale_export_poExports a Portable Object (Template) file for a language
_locale_export_po_submitProcess a locale export form submissions.
_locale_export_printPrint out a string on multiple lines
_locale_export_remove_pluralRemoves plural index information from a string
_locale_export_wrapCustom word wrapping for Portable Object (Template) files.
_locale_get_iso639_listSome of the common languages with their English and native names
_locale_import_append_pluralModify a string to contain proper count indices
_locale_import_one_stringImports a string into the database
_locale_import_parse_arithmeticParses and sanitizes an arithmetic formula into a PHP expression
_locale_import_parse_headerParses a Gettext Portable Object file header
_locale_import_parse_plural_formsParses a Plural-Forms entry from a Gettext Portable Object file header
_locale_import_parse_quotedParses a string in quotes
_locale_import_poParses Gettext Portable Object file information and inserts into database
_locale_import_read_poParses Gettext Portable Object file into an array
_locale_import_shorten_commentsGenerate a short, one string version of the passed comment array
_locale_import_tokenize_formulaBackward compatible implementation of token_get_all() for formula parsing
_locale_prepare_iso_listPrepares the language code list for a select form item with only the unsupported ones
_locale_string_deleteDelete a language string.
_locale_string_editUser interface for string editing.
_locale_string_edit_submitProcess string editing form submissions. Saves all translations of one string submitted from a form.
_locale_string_language_listList languages in search result table
_locale_string_seekPerform a string search and display results in a table
_locale_string_seek_formUser interface for the string search screen
_locale_string_seek_queryBuild object out of search criteria specified in request variables

File

includes/locale.inc
View source
  1. <?php
  2. /**
  3. * @file
  4. * Admin-related functions for locale.module.
  5. */
  6. // ---------------------------------------------------------------------------------
  7. // Language addition functionality (administration only)
  8. /**
  9. * Helper function to add a language
  10. */
  11. function _locale_add_language($code, $name, $onlylanguage = TRUE) {
  12. db_query("INSERT INTO {locales_meta} (locale, name) VALUES ('%s','%s')", $code, $name);
  13. $result = db_query("SELECT lid FROM {locales_source}");
  14. while ($string = db_fetch_object($result)) {
  15. db_query("INSERT INTO {locales_target} (lid, locale, translation) VALUES (%d,'%s', '')", $string->lid, $code);
  16. }
  17. // If only the language was added, and not a PO file import triggered
  18. // the language addition, we need to inform the user on how to start
  19. // a translation
  20. if ($onlylanguage) {
  21. drupal_set_message(t('The language %locale has been created, and can now be used to import a translation. More information is available in the <a href="%locale-help">help screen</a>.', array('%locale' => theme('placeholder', t($name)), '%locale-help' => url('admin/help/locale'))));
  22. }
  23. else {
  24. drupal_set_message(t('The language %locale has been created.', array('%locale' => theme('placeholder', t($name)))));
  25. }
  26. watchdog('locale', t('The %language language (%locale) has been created.', array('%language' => theme('placeholder', $name), '%locale' => theme('placeholder', $code))));
  27. }
  28. /**
  29. * User interface for the language management screen.
  30. */
  31. function _locale_admin_manage_screen() {
  32. $languages = locale_supported_languages(TRUE, TRUE);
  33. $options = array();
  34. $form['name'] = array('#tree' => TRUE);
  35. foreach ($languages['name'] as $key => $lang) {
  36. $options[$key] = '';
  37. $status = db_fetch_object(db_query("SELECT isdefault, enabled FROM {locales_meta} WHERE locale = '%s'", $key));
  38. if ($status->enabled) {
  39. $enabled[] = $key;
  40. }
  41. if ($status->isdefault) {
  42. $isdefault = $key;
  43. }
  44. if ($key == 'en') {
  45. $form['name']['en'] = array('#value' => check_plain($lang));
  46. }
  47. else {
  48. $original = db_fetch_object(db_query("SELECT COUNT(*) AS strings FROM {locales_source}"));
  49. $translation = db_fetch_object(db_query("SELECT COUNT(*) AS translation FROM {locales_target} WHERE locale = '%s' AND translation != ''", $key));
  50. $ratio = ($original->strings > 0 && $translation->translation > 0) ? round(($translation->translation/$original->strings)*100., 2) : 0;
  51. $form['name'][$key] = array('#type' => 'textfield',
  52. '#default_value' => $lang,
  53. '#size' => 15,
  54. '#maxlength' => 64,
  55. );
  56. $form['translation'][$key] = array('#value' => "$translation->translation/$original->strings ($ratio%)");
  57. }
  58. }
  59. $form['enabled'] = array('#type' => 'checkboxes',
  60. '#options' => $options,
  61. '#default_value' => $enabled,
  62. );
  63. $form['site_default'] = array('#type' => 'radios',
  64. '#options' => $options,
  65. '#default_value' => $isdefault,
  66. );
  67. $form['submit'] = array('#type' => 'submit', '#value' => t('Save configuration'));
  68. return drupal_get_form('_locale_admin_manage_screen', $form, 'locale_admin_manage_screen');
  69. }
  70. /**
  71. * Theme the locale admin manager form.
  72. */
  73. function theme_locale_admin_manage_screen($form) {
  74. foreach ($form['name'] as $key => $element) {
  75. // Do not take form control structures.
  76. if (is_array($element) && element_child($key)) {
  77. $rows[] = array(check_plain($key), form_render($form['name'][$key]), form_render($form['enabled'][$key]), form_render($form['site_default'][$key]), ($key != 'en' ? form_render($form['translation'][$key]) : message_na()), ($key != 'en' ? l(t('delete'), 'admin/locale/language/delete/'. $key) : ''));
  78. }
  79. }
  80. $header = array(array('data' => t('Code')), array('data' => t('English name')), array('data' => t('Enabled')), array('data' => t('Default')), array('data' => t('Translated')), array('data' => t('Operations')));
  81. $output = theme('table', $header, $rows);
  82. $output .= form_render($form);
  83. return $output;
  84. }
  85. /**
  86. * Process locale admin manager form submissions.
  87. */
  88. function _locale_admin_manage_screen_submit($form_id, $form_values) {
  89. // Save changes to existing languages.
  90. $languages = locale_supported_languages(FALSE, TRUE);
  91. foreach($languages['name'] as $key => $value) {
  92. if ($form_values['site_default'] == $key) {
  93. $form_values['enabled'][$key] = 1; // autoenable the default language
  94. }
  95. $enabled = $form_values['enabled'][$key] ? 1 : 0;
  96. if ($key == 'en') {
  97. // Disallow name change for English locale.
  98. db_query("UPDATE {locales_meta} SET isdefault = %d, enabled = %d WHERE locale = 'en'", ($form_values['site_default'] == $key), $enabled);
  99. }
  100. else {
  101. db_query("UPDATE {locales_meta} SET name = '%s', isdefault = %d, enabled = %d WHERE locale = '%s'", $form_values['name'][$key], ($form_values['site_default'] == $key), $enabled, $key);
  102. }
  103. }
  104. drupal_set_message(t('Configuration saved.'));
  105. // Changing the locale settings impacts the interface:
  106. cache_clear_all();
  107. return 'admin/locale/language/overview';
  108. }
  109. /**
  110. * User interface for the language addition screen.
  111. */
  112. function _locale_admin_manage_add_screen() {
  113. $isocodes = _locale_prepare_iso_list();
  114. $form = array();
  115. $form['language list'] = array('#type' => 'fieldset',
  116. '#title' => t('Language list'),
  117. '#collapsible' => TRUE,
  118. );
  119. $form['language list']['langcode'] = array('#type' => 'select',
  120. '#title' => t('Language name'),
  121. '#default_value' => key($isocodes),
  122. '#options' => $isocodes,
  123. '#description' => t('Select your language here, or add it below, if you are unable to find it.'),
  124. );
  125. $form['language list']['submit'] = array('#type' => 'submit', '#value' => t('Add language'));
  126. $output = drupal_get_form('locale_add_language_form', $form);
  127. $form = array();
  128. $form['custom language'] = array('#type' => 'fieldset',
  129. '#title' => t('Custom language'),
  130. '#collapsible' => TRUE,
  131. );
  132. $form['custom language']['langcode'] = array('#type' => 'textfield',
  133. '#title' => t('Language code'),
  134. '#size' => 12,
  135. '#maxlength' => 60,
  136. '#required' => TRUE,
  137. '#description' => t("Commonly this is an <a href=\"%iso-codes\">ISO 639 language code</a> with an optional country code for regional variants. Examples include 'en', 'en-US' and 'zh-cn'.", array('%iso-codes' => 'http://www.w3.org/WAI/ER/IG/ert/iso639.htm')),
  138. );
  139. $form['custom language']['langname'] = array('#type' => 'textfield',
  140. '#title' => t('Language name in English'),
  141. '#maxlength' => 64,
  142. '#required' => TRUE,
  143. '#description' => t('Name of the language. Will be available for translation in all languages.'),
  144. );
  145. $form['custom language']['submit'] = array('#type' => 'submit', '#value' => t('Add custom language'));
  146. // Use the validation and submit functions of the add language form.
  147. $output .= drupal_get_form('locale_custom_language_form', $form, 'locale_add_language_form');
  148. return $output;
  149. }
  150. /**
  151. * Validate the language addition form.
  152. */
  153. function locale_add_language_form_validate($form_id, $form_values) {
  154. if ($duplicate = db_num_rows(db_query("SELECT locale FROM {locales_meta} WHERE locale = '%s'", $form_values['langcode'])) != 0) {
  155. form_set_error(t('The language %language (%code) already exists.', array('%language' => theme('placeholder', check_plain($form_values['langname'])), '%code' => theme('placeholder', $form_values['langcode']))));
  156. }
  157. if (!isset($form_values['langname'])) {
  158. $isocodes = _locale_get_iso639_list();
  159. if (!isset($isocodes[$form_values['langcode']])) {
  160. form_set_error('langcode', t('Invalid language code.'));
  161. }
  162. }
  163. }
  164. /**
  165. * Process the language addition form submission.
  166. */
  167. function locale_add_language_form_submit($form_id, $form_values) {
  168. if (isset($form_values['langname'])) {
  169. // Custom language form.
  170. _locale_add_language($form_values['langcode'], $form_values['langname']);
  171. }
  172. else {
  173. $isocodes = _locale_get_iso639_list();
  174. _locale_add_language($form_values['langcode'], $isocodes[$form_values['langcode']][0]);
  175. }
  176. return 'admin/locale';
  177. }
  178. /**
  179. * User interface for the translation import screen.
  180. */
  181. function _locale_admin_import_screen() {
  182. $languages = locale_supported_languages(FALSE, TRUE);
  183. $languages = array_map('t', $languages['name']);
  184. unset($languages['en']);
  185. if (!count($languages)) {
  186. $languages = _locale_prepare_iso_list();
  187. }
  188. else {
  189. $languages = array(
  190. t('Already added languages') => $languages,
  191. t('Languages not yet added') => _locale_prepare_iso_list()
  192. );
  193. }
  194. $form = array();
  195. $form['import'] = array('#type' => 'fieldset',
  196. '#title' => t('Import translation'),
  197. );
  198. $form['import']['file'] = array('#type' => 'file',
  199. '#title' => t('Language file'),
  200. '#size' => 50,
  201. '#description' => t('A gettext Portable Object (.po) file.'),
  202. );
  203. $form['import']['langcode'] = array('#type' => 'select',
  204. '#title' => t('Import into'),
  205. '#options' => $languages,
  206. '#description' => t('Choose the language you want to add strings into. If you choose a language which is not yet set up, then it will be added.'),
  207. );
  208. $form['import']['mode'] = array('#type' => 'radios',
  209. '#title' => t('Mode'),
  210. '#default_value' => 'overwrite',
  211. '#options' => array('overwrite' => t('Strings in the uploaded file replace existing ones, new ones are added'), 'keep' => t('Existing strings are kept, only new strings are added')),
  212. );
  213. $form['import']['submit'] = array('#type' => 'submit', '#value' => t('Import'));
  214. $form['#attributes']['enctype'] = 'multipart/form-data';
  215. return drupal_get_form('_locale_admin_import', $form);
  216. }
  217. /**
  218. * Process the locale import form submission.
  219. */
  220. function _locale_admin_import_submit($form_id, $form_values) {
  221. // Add language, if not yet supported
  222. $languages = locale_supported_languages(TRUE, TRUE);
  223. if (!isset($languages['name'][$form_values['langcode']])) {
  224. $isocodes = _locale_get_iso639_list();
  225. _locale_add_language($form_values['langcode'], $isocodes[$form_values['langcode']][0], FALSE);
  226. }
  227. // Now import strings into the language
  228. $file = file_check_upload('file');
  229. if ($ret = _locale_import_po($file, $form_values['langcode'], $form_values['mode']) == FALSE) {
  230. $message = t('The translation import of %filename failed.', array('%filename' => theme('placeholder', $file->filename)));
  231. drupal_set_message($message, 'error');
  232. watchdog('locale', $message, WATCHDOG_ERROR);
  233. }
  234. return 'admin/locale';
  235. }
  236. /**
  237. * User interface for the translation export screen
  238. */
  239. function _locale_admin_export_screen() {
  240. $languages = locale_supported_languages(FALSE, TRUE);
  241. $languages = array_map('t', $languages['name']);
  242. unset($languages['en']);
  243. // Offer language specific export if any language is set up
  244. if (count($languages)) {
  245. $form = array();
  246. $form['export'] = array('#type' => 'fieldset',
  247. '#title' => t('Export translation'),
  248. '#collapsible' => TRUE,
  249. );
  250. $form['export']['langcode'] = array('#type' => 'select',
  251. '#title' => t('Language name'),
  252. '#options' => $languages,
  253. '#description' => t('Select the language you would like to export in gettext Portable Object (.po) format.'),
  254. );
  255. $form['export']['submit'] = array('#type' => 'submit', '#value' => t('Export'));
  256. $output = drupal_get_form('_locale_export_po', $form);
  257. }
  258. // Complete template export of the strings
  259. $form = array();
  260. $form['export'] = array('#type' => 'fieldset',
  261. '#title' => t('Export template'),
  262. '#collapsible' => TRUE,
  263. '#description' => t('Generate a gettext Portable Object Template (.pot) file with all the interface strings from the Drupal locale database.'),
  264. );
  265. $form['export']['submit'] = array('#type' => 'submit', '#value' => t('Export'));
  266. $output .= drupal_get_form('_locale_export_pot', $form, '_locale_export_po');
  267. return $output;
  268. }
  269. /**
  270. * Process a locale export form submissions.
  271. */
  272. function _locale_export_po_submit($form_id, $form_values) {
  273. _locale_export_po($form_values['langcode']);
  274. }
  275. /**
  276. * User interface for the string search screen
  277. */
  278. function _locale_string_seek_form() {
  279. // Get *all* languages set up
  280. $languages = locale_supported_languages(FALSE, TRUE);
  281. asort($languages['name']); unset($languages['name']['en']);
  282. $languages['name'] = array_map('check_plain', $languages['name']);
  283. // Present edit form preserving previous user settings
  284. $query = _locale_string_seek_query();
  285. $form = array();
  286. $form['search'] = array('#type' => 'fieldset',
  287. '#title' => t('Search'),
  288. );
  289. $form['search']['string'] = array('#type' => 'textfield',
  290. '#title' => t('Strings to search for'),
  291. '#default_value' => $query->string,
  292. '#size' => 30,
  293. '#maxlength' => 30,
  294. '#description' => t('Leave blank to show all strings. The search is case sensitive.'),
  295. );
  296. $form['search']['language'] = array('#type' => 'radios',
  297. '#title' => t('Language'),
  298. '#default_value' => ($query->language ? $query->language : 'all'),
  299. '#options' => array_merge(array('all' => t('All languages'), 'en' => t('English (provided by Drupal)')), $languages['name']),
  300. );
  301. $form['search']['searchin'] = array('#type' => 'radios',
  302. '#title' => t('Search in'),
  303. '#default_value' => ($query->searchin ? $query->searchin : 'all'),
  304. '#options' => array('all' => t('All strings in that language'), 'translated' => t('Only translated strings'), 'untranslated' => t('Only untranslated strings')),
  305. );
  306. $form['search']['submit'] = array('#type' => 'submit', '#value' => t('Search'));
  307. $form['#redirect'] = FALSE;
  308. return drupal_get_form('_locale_string_seek', $form);
  309. }
  310. /**
  311. * User interface for string editing.
  312. */
  313. function _locale_string_edit($lid) {
  314. $languages = locale_supported_languages(FALSE, TRUE);
  315. unset($languages['name']['en']);
  316. $result = db_query('SELECT DISTINCT s.source, t.translation, t.locale FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid WHERE s.lid = %d', $lid);
  317. $form = array();
  318. $form['translations'] = array('#tree' => TRUE);
  319. while ($translation = db_fetch_object($result)) {
  320. $orig = $translation->source;
  321. // Approximate the number of rows in a textfield with a maximum of 10.
  322. $rows = min(ceil(str_word_count($orig) / 12), 10);
  323. $form['translations'][$translation->locale] = array(
  324. '#type' => 'textarea',
  325. '#title' => $languages['name'][$translation->locale],
  326. '#default_value' => $translation->translation,
  327. '#rows' => $rows,
  328. );
  329. unset($languages['name'][$translation->locale]);
  330. }
  331. // Handle erroneous lid.
  332. if (!isset($orig)){
  333. drupal_set_message(t('String not found.'));
  334. drupal_goto('admin/locale/string/search');
  335. }
  336. // Add original text. Assign negative weight so that it floats to the top.
  337. $form['item'] = array('#type' => 'item',
  338. '#title' => t('Original text'),
  339. '#value' => check_plain(wordwrap($orig, 0)),
  340. '#weight' => -1,
  341. );
  342. foreach ($languages['name'] as $key => $lang) {
  343. $form['translations'][$key] = array(
  344. '#type' => 'textarea',
  345. '#title' => $lang,
  346. '#rows' => $rows,
  347. );
  348. }
  349. $form['lid'] = array('#type' => 'value', '#value' => $lid);
  350. $form['submit'] = array('#type' => 'submit', '#value' => t('Save translations'));
  351. return drupal_get_form('_locale_string_edit', $form);
  352. }
  353. /**
  354. * Process string editing form submissions.
  355. * Saves all translations of one string submitted from a form.
  356. */
  357. function _locale_string_edit_submit($form_id, $form_values) {
  358. $lid = $form_values['lid'];
  359. foreach ($form_values['translations'] as $key => $value) {
  360. $value = str_replace('&amp;', '&', filter_xss_admin($value));
  361. $trans = db_fetch_object(db_query("SELECT translation FROM {locales_target} WHERE lid = %d AND locale = '%s'", $lid, $key));
  362. if (isset($trans->translation)) {
  363. db_query("UPDATE {locales_target} SET translation = '%s' WHERE lid = %d AND locale = '%s'", $value, $lid, $key);
  364. }
  365. else {
  366. db_query("INSERT INTO {locales_target} (lid, translation, locale) VALUES (%d, '%s', '%s')", $lid, $value, $key);
  367. }
  368. }
  369. drupal_set_message(t('The string has been saved.'));
  370. // Refresh the locale cache.
  371. locale_refresh_cache();
  372. // Rebuild the menu, strings may have changed.
  373. menu_rebuild();
  374. return 'admin/locale/string/search';
  375. }
  376. /**
  377. * Delete a language string.
  378. */
  379. function _locale_string_delete($lid) {
  380. db_query('DELETE FROM {locales_source} WHERE lid = %d', $lid);
  381. db_query('DELETE FROM {locales_target} WHERE lid = %d', $lid);
  382. locale_refresh_cache();
  383. drupal_set_message(t('The string has been removed.'));
  384. drupal_goto('admin/locale/string/search');
  385. }
  386. /**
  387. * Parses Gettext Portable Object file information and inserts into database
  388. *
  389. * @param $file Object contains properties of local file to be imported
  390. * @param $lang Language code
  391. * @param $mode should existing translations be replaced?
  392. */
  393. function _locale_import_po($file, $lang, $mode) {
  394. // If not in 'safe mode', increase the maximum execution time:
  395. if (!ini_get('safe_mode')) {
  396. set_time_limit(240);
  397. }
  398. // Check if we have the language already in the database
  399. if (!db_fetch_object(db_query("SELECT locale FROM {locales_meta} WHERE locale = '%s'", $lang))) {
  400. drupal_set_message(t('The language selected for import is not supported.'), 'error');
  401. return FALSE;
  402. }
  403. // Get strings from file (returns on failure after a partial import, or on success)
  404. $status = _locale_import_read_po($file, $mode, $lang);
  405. if ($status === FALSE) {
  406. // error messages are set in _locale_import_read_po
  407. return FALSE;
  408. }
  409. // Get status information on import process
  410. list($headerdone, $additions, $updates) = _locale_import_one_string('report', $mode);
  411. if (!$headerdone) {
  412. drupal_set_message(t('The translation file %filename appears to have a missing or malformed header.', array('%filename' => theme('placeholder', $file->filename))), 'error');
  413. }
  414. // rebuild locale cache
  415. cache_clear_all("locale:$lang");
  416. // rebuild the menu, strings may have changed
  417. menu_rebuild();
  418. drupal_set_message(t('The translation was successfully imported. There are %number newly created translated strings and %update strings were updated.', array('%number' => $additions, '%update' => $updates)));
  419. watchdog('locale', t('Imported %file into %locale: %number new strings added and %update updated.', array('%file' => theme('placeholder', $file->filename), '%locale' => theme('placeholder', $lang), '%number' => $additions, '%update' => $updates)));
  420. return TRUE;
  421. }
  422. /**
  423. * Parses Gettext Portable Object file into an array
  424. *
  425. * @param $file Object with properties of local file to parse
  426. * @author Jacobo Tarrio
  427. */
  428. function _locale_import_read_po($file, $mode, $lang) {
  429. $message = theme('placeholder', $file->filename);
  430. $fd = fopen($file->filepath, "rb"); // File will get closed by PHP on return
  431. if (!$fd) {
  432. drupal_set_message(t('The translation import failed, because the file %filename could not be read.', array('%filename' => $message)), 'error');
  433. return FALSE;
  434. }
  435. $context = "COMMENT"; // Parser context: COMMENT, MSGID, MSGID_PLURAL, MSGSTR and MSGSTR_ARR
  436. $current = array(); // Current entry being read
  437. $plural = 0; // Current plural form
  438. $lineno = 0; // Current line
  439. while (!feof($fd)) {
  440. $line = fgets($fd, 10*1024); // A line should not be this long
  441. $lineno++;
  442. $line = trim(strtr($line, array("\\\n" => "")));
  443. if (!strncmp("#", $line, 1)) { // A comment
  444. if ($context == "COMMENT") { // Already in comment context: add
  445. $current["#"][] = substr($line, 1);
  446. }
  447. elseif (($context == "MSGSTR") || ($context == "MSGSTR_ARR")) { // End current entry, start a new one
  448. _locale_import_one_string($current, $mode, $lang);
  449. $current = array();
  450. $current["#"][] = substr($line, 1);
  451. $context = "COMMENT";
  452. }
  453. else { // Parse error
  454. drupal_set_message(t('The translation file %filename contains an error: "msgstr" was expected but not found on line %line.', array('%filename' => $message, '%line' => $lineno)), 'error');
  455. return FALSE;
  456. }
  457. }
  458. elseif (!strncmp("msgid_plural", $line, 12)) {
  459. if ($context != "MSGID") { // Must be plural form for current entry
  460. drupal_set_message(t('The translation file %filename contains an error: "msgid_plural" was expected but not found on line %line.', array('%filename' => $message, '%line' => $lineno)), 'error');
  461. return FALSE;
  462. }
  463. $line = trim(substr($line, 12));
  464. $quoted = _locale_import_parse_quoted($line);
  465. if ($quoted === false) {
  466. drupal_set_message(t('The translation file %filename contains a syntax error on line %line.', array('%filename' => $message, '%line' => $lineno)), 'error');
  467. return FALSE;
  468. }
  469. $current["msgid"] = $current["msgid"] ."\0". $quoted;
  470. $context = "MSGID_PLURAL";
  471. }
  472. elseif (!strncmp("msgid", $line, 5)) {
  473. if ($context == "MSGSTR") { // End current entry, start a new one
  474. _locale_import_one_string($current, $mode, $lang);
  475. $current = array();
  476. }
  477. elseif ($context == "MSGID") { // Already in this context? Parse error
  478. drupal_set_message(t('The translation file %filename contains an error: "msgid" is unexpected on line %line.', array('%filename' => $message, '%line' => $lineno)), 'error');
  479. return FALSE;
  480. }
  481. $line = trim(substr($line, 5));
  482. $quoted = _locale_import_parse_quoted($line);
  483. if ($quoted === false) {
  484. drupal_set_message(t('The translation file %filename contains a syntax error on line %line.', array('%filename' => $message, '%line' => $lineno)), 'error');
  485. return FALSE;
  486. }
  487. $current["msgid"] = $quoted;
  488. $context = "MSGID";
  489. }
  490. elseif (!strncmp("msgstr[", $line, 7)) {
  491. if (($context != "MSGID") && ($context != "MSGID_PLURAL") && ($context != "MSGSTR_ARR")) { // Must come after msgid, msgid_plural, or msgstr[]
  492. drupal_set_message(t('The translation file %filename contains an error: "msgstr[]" is unexpected on line %line.', array('%filename' => $message, '%line' => $lineno)), 'error');
  493. return FALSE;
  494. }
  495. if (strpos($line, "]") === false) {
  496. drupal_set_message(t('The translation file %filename contains a syntax error on line %line.', array('%filename' => $message, '%line' => $lineno)), 'error');
  497. return FALSE;
  498. }
  499. $frombracket = strstr($line, "[");
  500. $plural = substr($frombracket, 1, strpos($frombracket, "]") - 1);
  501. $line = trim(strstr($line, " "));
  502. $quoted = _locale_import_parse_quoted($line);
  503. if ($quoted === false) {
  504. drupal_set_message(t('The translation file %filename contains a syntax error on line %line.', array('%filename' => $message, '%line' => $lineno)), 'error');
  505. return FALSE;
  506. }
  507. $current["msgstr"][$plural] = $quoted;
  508. $context = "MSGSTR_ARR";
  509. }
  510. elseif (!strncmp("msgstr", $line, 6)) {
  511. if ($context != "MSGID") { // Should come just after a msgid block
  512. drupal_set_message(t('The translation file %filename contains an error: "msgstr" is unexpected on line %line.', array('%filename' => $message, '%line' => $lineno)), 'error');
  513. return FALSE;
  514. }
  515. $line = trim(substr($line, 6));
  516. $quoted = _locale_import_parse_quoted($line);
  517. if ($quoted === false) {
  518. drupal_set_message(t('The translation file %filename contains a syntax error on line %line.', array('%filename' => $message, '%line' => $lineno)), 'error');
  519. return FALSE;
  520. }
  521. $current["msgstr"] = $quoted;
  522. $context = "MSGSTR";
  523. }
  524. elseif ($line != "") {
  525. $quoted = _locale_import_parse_quoted($line);
  526. if ($quoted === false) {
  527. drupal_set_message(t('The translation file %filename contains a syntax error on line %line.', array('%filename' => $message, '%line' => $lineno)), 'error');
  528. return FALSE;
  529. }
  530. if (($context == "MSGID") || ($context == "MSGID_PLURAL")) {
  531. $current["msgid"] .= $quoted;
  532. }
  533. elseif ($context == "MSGSTR") {
  534. $current["msgstr"] .= $quoted;
  535. }
  536. elseif ($context == "MSGSTR_ARR") {
  537. $current["msgstr"][$plural] .= $quoted;
  538. }
  539. else {
  540. drupal_set_message(t('The translation file %filename contains an error: there is an unexpected string on line %line.', array('%filename' => $message, '%line' => $lineno)), 'error');
  541. return FALSE;
  542. }
  543. }
  544. }
  545. // End of PO file, flush last entry
  546. if (($context == "MSGSTR") || ($context == "MSGSTR_ARR")) {
  547. _locale_import_one_string($current, $mode, $lang);
  548. }
  549. elseif ($context != "COMMENT") {
  550. drupal_set_message(t('The translation file %filename ended unexpectedly at line %line.', array('%filename' => $message, '%line' => $lineno)), 'error');
  551. return FALSE;
  552. }
  553. }
  554. /**
  555. * Imports a string into the database
  556. *
  557. * @param $value Information about the string
  558. * @author Jacobo Tarrio
  559. */
  560. function _locale_import_one_string($value, $mode, $lang = NULL) {
  561. static $additions = 0;
  562. static $updates = 0;
  563. static $headerdone = FALSE;
  564. // Report the changes made (called at end of import)
  565. if ($value == 'report') {
  566. return array($headerdone, $additions, $updates);
  567. }
  568. // Current string is the header information
  569. elseif ($value['msgid'] == '') {
  570. $hdr = _locale_import_parse_header($value['msgstr']);
  571. // Get the plural formula
  572. if ($hdr["Plural-Forms"] && $p = _locale_import_parse_plural_forms($hdr["Plural-Forms"], $file->filename)) {
  573. list($nplurals, $plural) = $p;
  574. db_query("UPDATE {locales_meta} SET plurals = %d, formula = '%s' WHERE locale = '%s'", $nplurals, $plural, $lang);
  575. }
  576. else {
  577. db_query("UPDATE {locales_meta} SET plurals = %d, formula = '%s' WHERE locale = '%s'", 0, '', $lang);
  578. }
  579. $headerdone = TRUE;
  580. }
  581. // Some real string to import
  582. else {
  583. $comments = filter_xss_admin(_locale_import_shorten_comments($value['#']));
  584. // Handle a translation for some plural string
  585. if (strpos($value['msgid'], "\0")) {
  586. $english = explode("\0", $value['msgid'], 2);
  587. $entries = array_keys($value['msgstr']);
  588. for ($i = 3; $i <= count($entries); $i++) {
  589. $english[] = $english[1];
  590. }
  591. $translation = array_map('_locale_import_append_plural', $value['msgstr'], $entries);
  592. $english = array_map('_locale_import_append_plural', $english, $entries);
  593. foreach ($translation as $key => $trans) {
  594. if ($key == 0) {
  595. $plid = 0;
  596. }
  597. $loc = db_fetch_object(db_query("SELECT lid FROM {locales_source} WHERE source = '%s'", $english[$key]));
  598. if ($loc->lid) { // a string exists
  599. $lid = $loc->lid;
  600. // update location field
  601. db_query("UPDATE {locales_source} SET location = '%s' WHERE lid = %d", $comments, $lid);
  602. $trans2 = db_fetch_object(db_query("SELECT lid, translation, plid, plural FROM {locales_target} WHERE lid = %d AND locale = '%s'", $lid, $lang));
  603. if (!$trans2->lid) { // no translation in current language
  604. db_query("INSERT INTO {locales_target} (lid, locale, translation, plid, plural) VALUES (%d, '%s', '%s', %d, %d)", $lid, $lang, filter_xss_admin($trans), $plid, $key);
  605. $additions++;
  606. } // translation exists
  607. else if ($mode == 'overwrite' || $trans2->translation == '') {
  608. db_query("UPDATE {locales_target} SET translation = '%s', plid = %d, plural = %d WHERE locale = '%s' AND lid = %d", filter_xss_admin($trans), $plid, $key, $lang, $lid);
  609. if ($trans2->translation == '') {
  610. $additions++;
  611. }
  612. else {
  613. $updates++;
  614. }
  615. }
  616. }
  617. else { // no string
  618. db_query("INSERT INTO {locales_source} (location, source) VALUES ('%s', '%s')", $comments, filter_xss_admin($english[$key]));
  619. $loc = db_fetch_object(db_query("SELECT lid FROM {locales_source} WHERE source = '%s'", $english[$key]));
  620. $lid = $loc->lid;
  621. db_query("INSERT INTO {locales_target} (lid, locale, translation, plid, plural) VALUES (%d, '%s', '%s', %d, %d)", $lid, $lang, filter_xss_admin($trans), $plid, $key);
  622. if ($trans != '') {
  623. $additions++;
  624. }
  625. }
  626. $plid = $lid;
  627. }
  628. }
  629. // A simple translation
  630. else {
  631. $english = $value['msgid'];
  632. $translation = $value['msgstr'];
  633. $loc = db_fetch_object(db_query("SELECT lid FROM {locales_source} WHERE source = '%s'", $english));
  634. if ($loc->lid) { // a string exists
  635. $lid = $loc->lid;
  636. // update location field
  637. db_query("UPDATE {locales_source} SET location = '%s' WHERE source = '%s'", $comments, $english);
  638. $trans = db_fetch_object(db_query("SELECT lid, translation FROM {locales_target} WHERE lid = %d AND locale = '%s'", $lid, $lang));
  639. if (!$trans->lid) { // no translation in current language
  640. db_query("INSERT INTO {locales_target} (lid, locale, translation) VALUES (%d, '%s', '%s')", $lid, $lang, filter_xss_admin($translation));
  641. $additions++;
  642. } // translation exists
  643. else if ($mode == 'overwrite') { //overwrite in any case
  644. db_query("UPDATE {locales_target} SET translation = '%s' WHERE locale = '%s' AND lid = %d", filter_xss_admin($translation), $lang, $lid);
  645. if ($trans->translation == '') {
  646. $additions++;
  647. }
  648. else {
  649. $updates++;
  650. }
  651. } // overwrite if empty string
  652. else if ($trans->translation == '') {
  653. db_query("UPDATE {locales_target} SET translation = '%s' WHERE locale = '%s' AND lid = %d", $translation, $lang, $lid);
  654. $additions++;
  655. }
  656. }
  657. else { // no string
  658. db_query("INSERT INTO {locales_source} (location, source) VALUES ('%s', '%s')", $comments, $english);
  659. $loc = db_fetch_object(db_query("SELECT lid FROM {locales_source} WHERE source = '%s'", $english));
  660. $lid = $loc->lid;
  661. db_query("INSERT INTO {locales_target} (lid, locale, translation) VALUES (%d, '%s', '%s')", $lid, $lang, filter_xss_admin($translation));
  662. if ($translation != '') {
  663. $additions++;
  664. }
  665. }
  666. }
  667. }
  668. }
  669. /**
  670. * Parses a Gettext Portable Object file header
  671. *
  672. * @param $header A string containing the complete header
  673. * @return An associative array of key-value pairs
  674. * @author Jacobo Tarrio
  675. */
  676. function _locale_import_parse_header($header) {
  677. $hdr = array();
  678. $lines = explode("\n", $header);
  679. foreach ($lines as $line) {
  680. $line = trim($line);
  681. if ($line) {
  682. list($tag, $contents) = explode(":", $line, 2);
  683. $hdr[trim($tag)] = trim($contents);
  684. }
  685. }
  686. return $hdr;
  687. }
  688. /**
  689. * Parses a Plural-Forms entry from a Gettext Portable Object file header
  690. *
  691. * @param $pluralforms A string containing the Plural-Forms entry
  692. * @param $filename A string containing the filename
  693. * @return An array containing the number of plurals and a
  694. * formula in PHP for computing the plural form
  695. * @author Jacobo Tarrio
  696. */
  697. function _locale_import_parse_plural_forms($pluralforms, $filename) {
  698. // First, delete all whitespace
  699. $pluralforms = strtr($pluralforms, array(" " => "", "\t" => ""));
  700. // Select the parts that define nplurals and plural
  701. $nplurals = strstr($pluralforms, "nplurals=");
  702. if (strpos($nplurals, ";")) {
  703. $nplurals = substr($nplurals, 9, strpos($nplurals, ";") - 9);
  704. }
  705. else {
  706. return FALSE;
  707. }
  708. $plural = strstr($pluralforms, "plural=");
  709. if (strpos($plural, ";")) {
  710. $plural = substr($plural, 7, strpos($plural, ";") - 7);
  711. }
  712. else {
  713. return FALSE;
  714. }
  715. // Get PHP version of the plural formula
  716. $plural = _locale_import_parse_arithmetic($plural);
  717. if ($plural !== FALSE) {
  718. return array($nplurals, $plural);
  719. }
  720. else {
  721. drupal_set_message(t('The translation file %filename contains an error: the plural formula could not be parsed.', array('%filename' => theme('placeholder', $filename))), 'error');
  722. return FALSE;
  723. }
  724. }
  725. /**
  726. * Parses and sanitizes an arithmetic formula into a PHP expression
  727. *
  728. * While parsing, we ensure, that the operators have the right
  729. * precedence and associativity.
  730. *
  731. * @param $string A string containing the arithmetic formula
  732. * @return The PHP version of the formula
  733. * @author Jacobo Tarrio
  734. */
  735. function _locale_import_parse_arithmetic($string) {
  736. // Operator precedence table
  737. $prec = array("(" => -1, ")" => -1, "?" => 1, ":" => 1, "||" => 3, "&&" => 4, "==" => 5, "!=" => 5, "<" => 6, ">" => 6, "<=" => 6, ">=" => 6, "+" => 7, "-" => 7, "*" => 8, "/" => 8, "%" => 8);
  738. // Right associativity
  739. $rasc = array("?" => 1, ":" => 1);
  740. $tokens = _locale_import_tokenize_formula($string);
  741. // Parse by converting into infix notation then back into postfix
  742. $opstk = array();
  743. $elstk = array();
  744. foreach ($tokens as $token) {
  745. $ctok = $token;
  746. // Numbers and the $n variable are simply pushed into $elarr
  747. if (is_numeric($token)) {
  748. $elstk[] = $ctok;
  749. }
  750. elseif ($ctok == "n") {
  751. $elstk[] = '$n';
  752. }
  753. elseif ($ctok == "(") {
  754. $opstk[] = $ctok;
  755. }
  756. elseif ($ctok == ")") {
  757. $topop = array_pop($opstk);
  758. while (($topop != NULL) && ($topop != "(")) {
  759. $elstk[] = $topop;
  760. $topop = array_pop($opstk);
  761. }
  762. }
  763. elseif ($prec[$ctok]) {
  764. // If it's an operator, then pop from $oparr into $elarr until the
  765. // precedence in $oparr is less than current, then push into $oparr
  766. $topop = array_pop($opstk);
  767. while (($topop != NULL) && ($prec[$topop] >= $prec[$ctok]) && !(($prec[$topop] == $prec[$ctok]) && $rasc[$topop] && $rasc[$ctok])) {
  768. $elstk[] = $topop;
  769. $topop = array_pop($opstk);
  770. }
  771. if ($topop) {
  772. $opstk[] = $topop; // Return element to top
  773. }
  774. $opstk[] = $ctok; // Parentheses are not needed
  775. }
  776. else {
  777. return false;
  778. }
  779. }
  780. // Flush operator stack
  781. $topop = array_pop($opstk);
  782. while ($topop != NULL) {
  783. $elstk[] = $topop;
  784. $topop = array_pop($opstk);
  785. }
  786. // Now extract formula from stack
  787. $prevsize = count($elstk) + 1;
  788. while (count($elstk) < $prevsize) {
  789. $prevsize = count($elstk);
  790. for ($i = 2; $i < count($elstk); $i++) {
  791. $op = $elstk[$i];
  792. if ($prec[$op]) {
  793. $f = "";
  794. if ($op == ":") {
  795. $f = $elstk[$i - 2] ."):". $elstk[$i - 1] .")";
  796. }
  797. elseif ($op == "?") {
  798. $f = "(". $elstk[$i - 2] ."?(". $elstk[$i - 1];
  799. }
  800. else {
  801. $f = "(". $elstk[$i - 2] . $op . $elstk[$i - 1] .")";
  802. }
  803. array_splice($elstk, $i - 2, 3, $f);
  804. break;
  805. }
  806. }
  807. }
  808. // If only one element is left, the number of operators is appropriate
  809. if (count($elstk) == 1) {
  810. return $elstk[0];
  811. }
  812. else {
  813. return FALSE;
  814. }
  815. }
  816. /**
  817. * Backward compatible implementation of token_get_all() for formula parsing
  818. *
  819. * @param $string A string containing the arithmetic formula
  820. * @return The PHP version of the formula
  821. * @author Gerhard Killesreiter
  822. */
  823. function _locale_import_tokenize_formula($formula) {
  824. $formula = str_replace(" ", "", $formula);
  825. $tokens = array();
  826. for ($i = 0; $i < strlen($formula); $i++) {
  827. if (is_numeric($formula[$i])) {
  828. $num = $formula[$i];
  829. $j = $i + 1;
  830. while($j < strlen($formula) && is_numeric($formula[$j])) {
  831. $num .= $formula[$j];
  832. $j++;
  833. }
  834. $i = $j - 1;
  835. $tokens[] = $num;
  836. }
  837. elseif ($pos = strpos(" =<>!&|", $formula[$i])) { // We won't have a space
  838. $next = $formula[$i + 1];
  839. switch ($pos) {
  840. case 1:
  841. case 2:
  842. case 3:
  843. case 4:
  844. if ($next == '=') {
  845. $tokens[] = $formula[$i] .'=';
  846. $i++;
  847. }
  848. else {
  849. $tokens[] = $formula[$i];
  850. }
  851. break;
  852. case 5:
  853. if ($next == '&') {
  854. $tokens[] = '&&';
  855. $i++;
  856. }
  857. else {
  858. $tokens[] = $formula[$i];
  859. }
  860. break;
  861. case 6:
  862. if ($next == '|') {
  863. $tokens[] = '||';
  864. $i++;
  865. }
  866. else {
  867. $tokens[] = $formula[$i];
  868. }
  869. break;
  870. }
  871. }
  872. else {
  873. $tokens[] = $formula[$i];
  874. }
  875. }
  876. return $tokens;
  877. }
  878. /**
  879. * Modify a string to contain proper count indices
  880. *
  881. * This is a callback function used via array_map()
  882. *
  883. * @param $entry An array element
  884. * @param $key Index of the array element
  885. */
  886. function _locale_import_append_plural($entry, $key) {
  887. // No modifications for 0, 1
  888. if ($key == 0 || $key == 1) {
  889. return $entry;
  890. }
  891. // First remove any possibly false indices, then add new ones
  892. $entry = preg_replace('/(%count)\[[0-9]\]/', '\\1', $entry);
  893. return preg_replace('/(%count)/', "\\1[$key]", $entry);
  894. }
  895. /**
  896. * Generate a short, one string version of the passed comment array
  897. *
  898. * @param $comment An array of strings containing a comment
  899. * @return Short one string version of the comment
  900. */
  901. function _locale_import_shorten_comments($comment) {
  902. $comm = '';
  903. while (count($comment)) {
  904. $test = $comm . substr(array_shift($comment), 1) .', ';
  905. if (strlen($comm) < 130) {
  906. $comm = $test;
  907. }
  908. else {
  909. break;
  910. }
  911. }
  912. return substr($comm, 0, -2);
  913. }
  914. /**
  915. * Parses a string in quotes
  916. *
  917. * @param $string A string specified with enclosing quotes
  918. * @return The string parsed from inside the quotes
  919. */
  920. function _locale_import_parse_quoted($string) {
  921. if (substr($string, 0, 1) != substr($string, -1, 1)) {
  922. return FALSE; // Start and end quotes must be the same
  923. }
  924. $quote = substr($string, 0, 1);
  925. $string = substr($string, 1, -1);
  926. if ($quote == '"') { // Double quotes: strip slashes
  927. return stripcslashes($string);
  928. }
  929. elseif ($quote == "'") { // Simple quote: return as-is
  930. return $string;
  931. }
  932. else {
  933. return FALSE; // Unrecognized quote
  934. }
  935. }
  936. /**
  937. * Exports a Portable Object (Template) file for a language
  938. *
  939. * @param $language Selects a language to generate the output for
  940. */
  941. function _locale_export_po($language) {
  942. global $user;
  943. // Get language specific strings, or all strings
  944. if ($language) {
  945. $meta = db_fetch_object(db_query("SELECT * FROM {locales_meta} WHERE locale = '%s'", $language));
  946. $result = db_query("SELECT s.lid, s.source, s.location, t.translation, t.plid, t.plural FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid WHERE t.locale = '%s' ORDER BY t.plid, t.plural", $language);
  947. }
  948. else {
  949. $result = db_query("SELECT s.lid, s.source, s.location, t.plid, t.plural FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid ORDER BY t.plid, t.plural");
  950. }
  951. // Build array out of the database results
  952. $parent = array();
  953. while ($child = db_fetch_object($result)) {
  954. if ($child->source != '') {
  955. $parent[$child->lid]['comment'] = $child->location;
  956. $parent[$child->lid]['msgid'] = $child->source;
  957. $parent[$child->lid]['translation'] = $child->translation;
  958. if ($child->plid) {
  959. $parent[$child->lid]['child'] = 1;
  960. $parent[$child->plid]['plural'] = $child->lid;
  961. }
  962. }
  963. }
  964. // Generating Portable Object file for a language
  965. if ($language) {
  966. $filename = $language .'.po';
  967. $header .= "# $meta->name translation of ". variable_get('site_name', 'Drupal') ."\n";
  968. $header .= '# Copyright (c) '. date('Y') .' '. $user->name .' <'. $user->mail .">\n";
  969. $header .= "#\n";
  970. $header .= "msgid \"\"\n";
  971. $header .= "msgstr \"\"\n";
  972. $header .= "\"Project-Id-Version: PROJECT VERSION\\n\"\n";
  973. $header .= "\"POT-Creation-Date: ". date("Y-m-d H:iO") ."\\n\"\n";
  974. $header .= "\"PO-Revision-Date: ". date("Y-m-d H:iO") ."\\n\"\n";
  975. $header .= "\"Last-Translator: ". $user->name .' <'. $user->mail .">\\n\"\n";
  976. $header .= "\"Language-Team: ". $meta->name .' <'. $user->mail .">\\n\"\n";
  977. $header .= "\"MIME-Version: 1.0\\n\"\n";
  978. $header .= "\"Content-Type: text/plain; charset=utf-8\\n\"\n";
  979. $header .= "\"Content-Transfer-Encoding: 8bit\\n\"\n";
  980. if ($meta->formula && $meta->plurals) {
  981. $header .= "\"Plural-Forms: nplurals=". $meta->plurals ."; plural=". strtr($meta->formula, array('$' => '')) .";\\n\"\n";
  982. }
  983. $header .= "\n";
  984. watchdog('locale', t('Exported %locale translation file: %filename.', array('%locale' => theme('placeholder', $meta->name), '%filename' => theme('placeholder', $filename))));
  985. }
  986. // Generating Portable Object Template
  987. else {
  988. $filename = 'drupal.pot';
  989. $header .= "# LANGUAGE translation of PROJECT\n";
  990. $header .= "# Copyright (c) YEAR NAME <EMAIL@ADDRESS>\n";
  991. $header .= "#\n";
  992. $header .= "msgid \"\"\n";
  993. $header .= "msgstr \"\"\n";
  994. $header .= "\"Project-Id-Version: PROJECT VERSION\\n\"\n";
  995. $header .= "\"POT-Creation-Date: ". date("Y-m-d H:iO") ."\\n\"\n";
  996. $header .= "\"PO-Revision-Date: YYYY-mm-DD HH:MM+ZZZZ\\n\"\n";
  997. $header .= "\"Last-Translator: NAME <EMAIL@ADDRESS>\\n\"\n";
  998. $header .= "\"Language-Team: LANGUAGE <EMAIL@ADDRESS>\\n\"\n";
  999. $header .= "\"MIME-Version: 1.0\\n\"\n";
  1000. $header .= "\"Content-Type: text/plain; charset=utf-8\\n\"\n";
  1001. $header .= "\"Content-Transfer-Encoding: 8bit\\n\"\n";
  1002. $header .= "\"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\\n\"\n";
  1003. $header .= "\n";
  1004. watchdog('locale', t('Exported translation file: %filename.', array('%filename' => theme('placeholder', $filename))));
  1005. }
  1006. // Start download process
  1007. header("Content-Disposition: attachment; filename=$filename");
  1008. header("Content-Type: text/plain; charset=utf-8");
  1009. print $header;
  1010. foreach ($parent as $lid => $message) {
  1011. if (!isset($message['child'])) {
  1012. if ($message['comment']) {
  1013. print '#: '. $message['comment'] ."\n";
  1014. }
  1015. print 'msgid '. _locale_export_print($message['msgid']);
  1016. if ($plural = $message['plural']) {
  1017. print 'msgid_plural '. _locale_export_print($parent[$plural]['msgid']);
  1018. if ($language) {
  1019. $translation = $message['translation'];
  1020. for ($i = 0; $i < $meta->plurals; $i++) {
  1021. print 'msgstr['. $i .'] '. _locale_export_print($translation);
  1022. if ($plural) {
  1023. $translation = $parent[$plural]['translation'];
  1024. if ($i > 1) {
  1025. $translation = _locale_export_remove_plural($translation);
  1026. }
  1027. $plural = $parent[$plural]['plural'];
  1028. }
  1029. else {
  1030. $translation = '';
  1031. }
  1032. }
  1033. }
  1034. else {
  1035. print 'msgstr[0] ""'. "\n";
  1036. print 'msgstr[1] ""'. "\n";
  1037. }
  1038. }
  1039. else {
  1040. if ($language) {
  1041. print 'msgstr '. _locale_export_print($message['translation']);
  1042. }
  1043. else {
  1044. print 'msgstr ""'. "\n";
  1045. }
  1046. }
  1047. print "\n";
  1048. }
  1049. }
  1050. die();
  1051. }
  1052. /**
  1053. * Print out a string on multiple lines
  1054. */
  1055. function _locale_export_print($str) {
  1056. $stri = addcslashes($str, "\0..\37\\\"");
  1057. $parts = array();
  1058. // Cut text into several lines
  1059. while ($stri != "") {
  1060. $i = strpos($stri, "\\n");
  1061. if ($i === FALSE) {
  1062. $curstr = $stri;
  1063. $stri = "";
  1064. }
  1065. else {
  1066. $curstr = substr($stri, 0, $i + 2);
  1067. $stri = substr($stri, $i + 2);
  1068. }
  1069. $curparts = explode("\n", _locale_export_wrap($curstr, 70));
  1070. $parts = array_merge($parts, $curparts);
  1071. }
  1072. if (count($parts) > 1) {
  1073. return "\"\"\n\"". implode("\"\n\"", $parts) ."\"\n";
  1074. }
  1075. else {
  1076. return "\"$parts[0]\"\n";
  1077. }
  1078. }
  1079. /**
  1080. * Custom word wrapping for Portable Object (Template) files.
  1081. *
  1082. * @author Jacobo Tarrio
  1083. */
  1084. function _locale_export_wrap($str, $len) {
  1085. $words = explode(' ', $str);
  1086. $ret = array();
  1087. $cur = "";
  1088. $nstr = 1;
  1089. while (count($words)) {
  1090. $word = array_shift($words);
  1091. if ($nstr) {
  1092. $cur = $word;
  1093. $nstr = 0;
  1094. }
  1095. elseif (strlen("$cur $word") > $len) {
  1096. $ret[] = $cur . " ";
  1097. $cur = $word;
  1098. }
  1099. else {
  1100. $cur = "$cur $word";
  1101. }
  1102. }
  1103. $ret[] = $cur;
  1104. return implode("\n", $ret);
  1105. }
  1106. /**
  1107. * Removes plural index information from a string
  1108. */
  1109. function _locale_export_remove_plural($entry) {
  1110. return preg_replace('/(%count)\[[0-9]\]/', '\\1', $entry);
  1111. }
  1112. /**
  1113. * List languages in search result table
  1114. */
  1115. function _locale_string_language_list($translation) {
  1116. $languages = locale_supported_languages(FALSE, TRUE);
  1117. unset($languages['name']['en']);
  1118. $output = '';
  1119. foreach ($languages['name'] as $key => $value) {
  1120. if (isset($translation[$key])) {
  1121. $output .= ($translation[$key] != '') ? $key .' ' : "<em class=\"locale-untranslated\">$key</em> ";
  1122. }
  1123. }
  1124. return $output;
  1125. }
  1126. /**
  1127. * Build object out of search criteria specified in request variables
  1128. */
  1129. function _locale_string_seek_query() {
  1130. static $query = NULL;
  1131. if (is_null($query)) {
  1132. $fields = array('string', 'language', 'searchin');
  1133. $query = new StdClass;
  1134. if (is_array($_REQUEST['edit'])) {
  1135. foreach ($_REQUEST['edit'] as $key => $value) {
  1136. if (!empty($value) && in_array($key, $fields)) {
  1137. $query->$key = $value;
  1138. }
  1139. }
  1140. }
  1141. else {
  1142. foreach ($_REQUEST as $key => $value) {
  1143. if (!empty($value) && in_array($key, $fields)) {
  1144. $query->$key = strpos(',', $value) ? explode(',', $value) : $value;
  1145. }
  1146. }
  1147. }
  1148. }
  1149. return $query;
  1150. }
  1151. /**
  1152. * Perform a string search and display results in a table
  1153. */
  1154. function _locale_string_seek() {
  1155. // We have at least one criterion to match
  1156. if ($query = _locale_string_seek_query()) {
  1157. $join = "SELECT s.source, s.location, s.lid, t.translation, t.locale FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid ";
  1158. $arguments = array();
  1159. // Compute LIKE section
  1160. switch ($query->searchin) {
  1161. case 'translated':
  1162. $where = "WHERE (t.translation LIKE '%%%s%%' AND t.translation != '')";
  1163. $orderby = "ORDER BY t.translation";
  1164. $arguments[] = $query->string;
  1165. break;
  1166. case 'untranslated':
  1167. $where = "WHERE (s.source LIKE '%%%s%%' AND t.translation = '')";
  1168. $orderby = "ORDER BY s.source";
  1169. $arguments[] = $query->string;
  1170. break;
  1171. case 'all' :
  1172. default:
  1173. $where = "WHERE (s.source LIKE '%%%s%%' OR t.translation LIKE '%%%s%%')";
  1174. $orderby = '';
  1175. $arguments[] = $query->string;
  1176. $arguments[] = $query->string;
  1177. break;
  1178. }
  1179. switch ($query->language) {
  1180. // Force search in source strings
  1181. case "en":
  1182. $sql = $join ." WHERE s.source LIKE '%%%s%%' ORDER BY s.source";
  1183. $arguments = array($query->string); // $where is not used, discard its arguments
  1184. break;
  1185. // Search in all languages
  1186. case "all":
  1187. $sql = "$join $where $orderby";
  1188. break;
  1189. // Some different language
  1190. default:
  1191. $sql = "$join $where AND t.locale = '%s' $orderby";
  1192. $arguments[] = $query->language;
  1193. }
  1194. $result = pager_query($sql, 50, 0, NULL, $arguments);
  1195. $header = array(t('String'), t('Locales'), array('data' => t('Operations'), 'colspan' => '2'));
  1196. $arr = array();
  1197. while ($locale = db_fetch_object($result)) {
  1198. $arr[$locale->lid]['locales'][$locale->locale] = $locale->translation;
  1199. $arr[$locale->lid]['location'] = $locale->location;
  1200. $arr[$locale->lid]['source'] = $locale->source;
  1201. }
  1202. foreach ($arr as $lid => $value) {
  1203. $rows[] = array(array('data' => check_plain(truncate_utf8($value['source'], 150, FALSE, TRUE)) .'<br /><small>'. $value['location'] .'</small>'), array('data' => _locale_string_language_list($value['locales']), 'align' => 'center'), array('data' => l(t('edit'), "admin/locale/string/edit/$lid"), 'class' => 'nowrap'), array('data' => l(t('delete'), "admin/locale/string/delete/$lid"), 'class' => 'nowrap'));
  1204. }
  1205. $request = array();
  1206. if (count($query)) {
  1207. foreach ($query as $key => $value) {
  1208. $request[$key] = (is_array($value)) ? implode(',', $value) : $value;
  1209. }
  1210. }
  1211. if (count($rows)) {
  1212. $output .= theme('table', $header, $rows);
  1213. }
  1214. if ($pager = theme('pager', NULL, 50, 0, $request)) {
  1215. $output .= $pager;
  1216. }
  1217. }
  1218. return $output;
  1219. }
  1220. // ---------------------------------------------------------------------------------
  1221. // List of some of the most common languages (administration only)
  1222. /**
  1223. * Prepares the language code list for a select form item with only the unsupported ones
  1224. */
  1225. function _locale_prepare_iso_list() {
  1226. $languages = locale_supported_languages(FALSE, TRUE);
  1227. $isocodes = _locale_get_iso639_list();
  1228. foreach ($isocodes as $key => $value) {
  1229. if (isset($languages['name'][$key])) {
  1230. unset($isocodes[$key]);
  1231. continue;
  1232. }
  1233. if (count($value) == 2) {
  1234. $tname = t($value[0]);
  1235. $isocodes[$key] = ($tname == $value[1]) ? $tname : "$tname ($value[1])";
  1236. }
  1237. else {
  1238. $isocodes[$key] = t($value[0]);
  1239. }
  1240. }
  1241. asort($isocodes);
  1242. return $isocodes;
  1243. }
  1244. /**
  1245. * Some of the common languages with their English and native names
  1246. *
  1247. * Based on ISO 639 and http://people.w3.org/rishida/names/languages.html
  1248. */
  1249. function _locale_get_iso639_list() {
  1250. return array(
  1251. "aa" => array("Afar"),
  1252. "ab" => array("Abkhazian", "аҧсуа бызшәа"),
  1253. "ae" => array("Avestan"),
  1254. "af" => array("Afrikaans"),
  1255. "ak" => array("Akan"),
  1256. "am" => array("Amharic", "አማርኛ"),
  1257. "ar" => array("Arabic", "العربية"),
  1258. "as" => array("Assamese"),
  1259. "av" => array("Avar"),
  1260. "ay" => array("Aymara"),
  1261. "az" => array("Azerbaijani", "azərbaycan"),
  1262. "ba" => array("Bashkir"),
  1263. "be" => array("Belarusian", "Беларуская"),
  1264. "bg" => array("Bulgarian", "Български"),
  1265. "bh" => array("Bihari"),
  1266. "bi" => array("Bislama"),
  1267. "bm" => array("Bambara", "Bamanankan"),
  1268. "bn" => array("Bengali"),
  1269. "bo" => array("Tibetan"),
  1270. "br" => array("Breton"),
  1271. "bs" => array("Bosnian", "Bosanski"),
  1272. "ca" => array("Catalan", "Català"),
  1273. "ce" => array("Chechen"),
  1274. "ch" => array("Chamorro"),
  1275. "co" => array("Corsican"),
  1276. "cr" => array("Cree"),
  1277. "cs" => array("Czech", "Čeština"),
  1278. "cu" => array("Old Slavonic"),
  1279. "cv" => array("Chuvash"),
  1280. "cy" => array("Welsh", "Cymraeg"),
  1281. "da" => array("Danish", "Dansk"),
  1282. "de" => array("German", "Deutsch"),
  1283. "dv" => array("Maldivian"),
  1284. "dz" => array("Bhutani"),
  1285. "ee" => array("Ewe", "Ɛʋɛ"),
  1286. "el" => array("Greek", "Ελληνικά"),
  1287. "en" => array("English"),
  1288. "eo" => array("Esperanto"),
  1289. "es" => array("Spanish", "Español"),
  1290. "et" => array("Estonian", "Eesti"),
  1291. "eu" => array("Basque", "Euskera"),
  1292. "fa" => array("Persian", "فارسی"),
  1293. "ff" => array("Fulah", "Fulfulde"),
  1294. "fi" => array("Finnish", "Suomi"),
  1295. "fj" => array("Fiji"),
  1296. "fo" => array("Faeroese"),
  1297. "fr" => array("French", "Français"),
  1298. "fy" => array("Frisian", "Frysk"),
  1299. "ga" => array("Irish", "Gaeilge"),
  1300. "gd" => array("Scots Gaelic"),
  1301. "gl" => array("Galician", "Galego"),
  1302. "gn" => array("Guarani"),
  1303. "gu" => array("Gujarati"),
  1304. "gv" => array("Manx"),
  1305. "ha" => array("Hausa"),
  1306. "he" => array("Hebrew", "עברית"),
  1307. "hi" => array("Hindi", "हिन्दी"),
  1308. "ho" => array("Hiri Motu"),
  1309. "hr" => array("Croatian", "Hrvatski"),
  1310. "hu" => array("Hungarian", "Magyar"),
  1311. "hy" => array("Armenian", "Հայերեն"),
  1312. "hz" => array("Herero"),
  1313. "ia" => array("Interlingua"),
  1314. "id" => array("Indonesian", "Bahasa Indonesia"),
  1315. "ie" => array("Interlingue"),
  1316. "ig" => array("Igbo"),
  1317. "ik" => array("Inupiak"),
  1318. "is" => array("Icelandic", "Íslenska"),
  1319. "it" => array("Italian", "Italiano"),
  1320. "iu" => array("Inuktitut"),
  1321. "ja" => array("Japanese", "日本語"),
  1322. "jv" => array("Javanese"),
  1323. "ka" => array("Georgian"),
  1324. "kg" => array("Kongo"),
  1325. "ki" => array("Kikuyu"),
  1326. "kj" => array("Kwanyama"),
  1327. "kk" => array("Kazakh", "Қазақ"),
  1328. "kl" => array("Greenlandic"),
  1329. "km" => array("Cambodian"),
  1330. "kn" => array("Kannada", "ಕನ್ನಡ"),
  1331. "ko" => array("Korean", "한국어"),
  1332. "kr" => array("Kanuri"),
  1333. "ks" => array("Kashmiri"),
  1334. "ku" => array("Kurdish", "Kurdî"),
  1335. "kv" => array("Komi"),
  1336. "kw" => array("Cornish"),
  1337. "ky" => array("Kirghiz", "Кыргыз"),
  1338. "la" => array("Latin", "Latina"),
  1339. "lb" => array("Luxembourgish"),
  1340. "lg" => array("Luganda"),
  1341. "ln" => array("Lingala"),
  1342. "lo" => array("Laothian"),
  1343. "lt" => array("Lithuanian", "Lietuviškai"),
  1344. "lv" => array("Latvian", "Latviešu"),
  1345. "mg" => array("Malagasy"),
  1346. "mh" => array("Marshallese"),
  1347. "mi" => array("Maori"),
  1348. "mk" => array("Macedonian", "Македонски"),
  1349. "ml" => array("Malayalam", "മലയാളം"),
  1350. "mn" => array("Mongolian"),
  1351. "mo" => array("Moldavian"),
  1352. "mr" => array("Marathi"),
  1353. "ms" => array("Malay", "Bahasa Melayu"),
  1354. "mt" => array("Maltese", "Malti"),
  1355. "my" => array("Burmese"),
  1356. "na" => array("Nauru"),
  1357. "nd" => array("North Ndebele"),
  1358. "ne" => array("Nepali"),
  1359. "ng" => array("Ndonga"),
  1360. "nl" => array("Dutch", "Nederlands"),
  1361. "no" => array("Norwegian", "Norsk"),
  1362. "nr" => array("South Ndebele"),
  1363. "nv" => array("Navajo"),
  1364. "ny" => array("Chichewa"),
  1365. "oc" => array("Occitan"),
  1366. "om" => array("Oromo"),
  1367. "or" => array("Oriya"),
  1368. "os" => array("Ossetian"),
  1369. "pa" => array("Punjabi"),
  1370. "pi" => array("Pali"),
  1371. "pl" => array("Polish", "Polski"),
  1372. "ps" => array("Pashto", "پښتو"),
  1373. "pt" => array("Portuguese", "Português"),
  1374. "qu" => array("Quechua"),
  1375. "rm" => array("Rhaeto-Romance"),
  1376. "rn" => array("Kirundi"),
  1377. "ro" => array("Romanian", "Română"),
  1378. "ru" => array("Russian", "Русский"),
  1379. "rw" => array("Kinyarwanda"),
  1380. "sa" => array("Sanskrit"),
  1381. "sc" => array("Sardinian"),
  1382. "sd" => array("Sindhi"),
  1383. "se" => array("Northern Sami"),
  1384. "sg" => array("Sango"),
  1385. "sh" => array("Serbo-Croatian"),
  1386. "si" => array("Singhalese"),
  1387. "sk" => array("Slovak", "Slovenčina"),
  1388. "sl" => array("Slovenian", "Slovenščina"),
  1389. "sm" => array("Samoan"),
  1390. "sn" => array("Shona"),
  1391. "so" => array("Somali"),
  1392. "sq" => array("Albanian", "Shqip"),
  1393. "sr" => array("Serbian", "Српски"),
  1394. "ss" => array("Siswati"),
  1395. "st" => array("Sesotho"),
  1396. "su" => array("Sudanese"),
  1397. "sv" => array("Swedish", "Svenska"),
  1398. "sw" => array("Swahili", "Kiswahili"),
  1399. "ta" => array("Tamil", "தமிழ்"),
  1400. "te" => array("Telugu", "తెలుగు"),
  1401. "tg" => array("Tajik"),
  1402. "th" => array("Thai", "ภาษาไทย"),
  1403. "ti" => array("Tigrinya"),
  1404. "tk" => array("Turkmen"),
  1405. "tl" => array("Tagalog"),
  1406. "tn" => array("Setswana"),
  1407. "to" => array("Tonga"),
  1408. "tr" => array("Turkish", "Türkçe"),
  1409. "ts" => array("Tsonga"),
  1410. "tt" => array("Tatar", "Tatarça"),
  1411. "tw" => array("Twi"),
  1412. "ty" => array("Tahitian"),
  1413. "ug" => array("Uighur"),
  1414. "uk" => array("Ukrainian", "Українська"),
  1415. "ur" => array("Urdu", "اردو"),
  1416. "uz" => array("Uzbek", "o'zbek"),
  1417. "ve" => array("Venda"),
  1418. "vi" => array("Vietnamese", "Tiếng Việt"),
  1419. "wo" => array("Wolof"),
  1420. "xh" => array("Xhosa", "isiXhosa"),
  1421. "yi" => array("Yiddish"),
  1422. "yo" => array("Yoruba", "Yorùbá"),
  1423. "za" => array("Zhuang"),
  1424. "zh-hans" => array("Chinese, Simplified", "简体中文"),
  1425. "zh-hant" => array("Chinese, Traditional", "繁體中文"),
  1426. "zu" => array("Zulu", "isiZulu"),
  1427. );
  1428. }
Login or register to post comments