You are here

i18nstrings.module in Internationalization 5.3

Same filename and directory in other branches
  1. 6 i18nstrings/i18nstrings.module

Internationalization (i18n) package - translattable strings

This (rewritten) module uses locale's tables to store source strings in the default language. The storage aims to be forward compatible with Drupal 6 locale (with textgroups) and i18nstrings 6.x

The 'string id' is some context information about the string, has several parts separated by ':'

  • textgroup, generic string group name (profile, content_type, etc..)
  • object type, like 'field', 'type''
  • object id
  • property

About the storage, as Drupal 5 doesn't have textgroups, it will use an aditional table (i18n_locale) that links together 'strid' and 'lid'. While there may be multiple objects using the same default string, locale tables will only have one instance.

@author Jose A. Reyero, 2007

File

i18nstrings/i18nstrings.module
View source
<?php

/**
 * @file
 * Internationalization (i18n) package - translattable strings
 * 
 * This (rewritten) module uses locale's tables to store source strings in the default language. The storage aims
 * to be forward compatible with Drupal 6 locale (with textgroups) and i18nstrings 6.x
 * 
 * The 'string id' is some context information about the string, has several parts separated by ':'
 * - textgroup, generic string group name (profile, content_type, etc..)
 * - object type, like 'field', 'type''
 * - object id
 * - property
 * 
 * About the storage, as Drupal 5 doesn't have textgroups, it will use an aditional table (i18n_locale)
 * that links together 'strid' and 'lid'. While there may be multiple objects using the same default
 * string, locale tables will only have one instance.
 * 
 * @author Jose A. Reyero, 2007
 *
 */

// This is a special char that shouldn't appear in paths and will help us store two strids on the same location
define('I18NSTRINGS_SPLIT', "\n");

/**
 * Implementation of hook_menu().
 */
function i18nstrings_menu($may_cache) {
  $items[] = array(
    'path' => 'admin/settings/i18n/strings',
    'type' => MENU_LOCAL_TASK,
    'title' => t('Strings'),
    'description' => t('Translatable strings.'),
    'callback' => 'i18nstrings_admin',
    'access' => user_access('administer site configuration'),
  );
  return $items;
}

/**
 * Menu callback. Administration page
 */
function i18nstrings_admin($op = NULL, $strid = NULL) {
  switch ($op) {
    case 'edit':
      return drupal_get_form('i18nstrings_admin_form', $strid);
    default:
      return i18nstrings_admin_overview();
  }
}

/**
 * List of strings
 */
function i18nstrings_admin_overview() {
  $output = '';
  $header = array(
    t('String Id'),
    t('Default'),
    '',
  );
  $result = db_query("SELECT DISTINCT(strid) FROM {i18n_locale} ORDER BY strid", i18n_default_language());
  $rows = array();
  while ($str = db_fetch_object($result)) {
    $rows[] = array(
      $str->strid,
      tt($str->strid),
      l(t('edit'), 'admin/settings/i18n/strings/edit/' . $str->strid),
    );
  }
  $output .= theme('table', $header, $rows);
  return $output;
}

/**
 * Form callback: i18nstrings_admin_form
 */
function i18nstrings_admin_form($strid) {
  $strings = i18nstrings_load($strid);
  $form['strid'] = array(
    '#type' => 'value',
    '#value' => $strid,
  );
  $form['languages'] = array(
    '#type' => 'fieldset',
    '#tree' => TRUE,
    '#title' => t('Translations'),
  );

  // Approximate the number of rows in a textfield with a maximum of 10.
  $trans = i18nstrings_get_string($strid, i18n_default_language());
  $default = !empty($trans->translation) ? $trans->translation : '';
  $rows = min(ceil(str_word_count($default) / 12), 10);
  foreach (i18n_supported_languages() as $language => $name) {
    $form['languages'][$language] = array(
      '#type' => 'textarea',
      '#rows' => $rows,
      '#title' => $name,
      '#default_value' => i18nstrings_get_string($strid, $language),
    );
  }
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save'),
  );
  return $form;
}

/**
 * Form submit callback
 */
function i18nstrings_admin_form_submit($form_id, $form_values) {
  $strid = $form_values['strid'];
  foreach (i18n_supported_languages() as $language => $name) {
    i18nstrings_save_string($strid, $language, $form_values['languages'][$language]);
  }
  drupal_set_message(t('The strings have been updated'));
  return 'admin/settings/i18n/strings';
}

/**
 * Load string translations
 */
function i18nstrings_load($strid) {
  $strings = array();
  $result = db_query("SELECT * FROM {i18n_locale} WHERE strid = '%s'", $strid);
  while ($str = db_fetch_object($result)) {
    $strings[$str->locale] = $str->text;
  }
}

/**
 * Get string for a language.
 * 
 * @return object The translation object if found with lid and translation properties
 */
function i18nstrings_get_string($strid, $language = FALSE, $default = NULL, $refresh = FALSE) {
  static $strings = array();
  if ($refresh) {
    $language ? $strings[$language] = array() : ($strings = array());
  }
  if (!isset($strings[$language][$strid])) {
    $translation = db_fetch_object(db_query("SELECT s.lid, t.translation FROM {i18n_locale} s LEFT JOIN {locales_target} t ON s.lid = t.lid AND t.locale = '%s' WHERE s.strid = '%s'", $language, $strid));
    if ($translation) {
      $strings[$language][$strid] = $translation->translation ? $translation : FALSE;
    }
    else {

      // Looks like this string is not in the collection, add it
      $source = i18nstrings_save_string($strid, $default);
      $strings[$language][$strid] = $source;
    }
  }
  return $strings[$language][$strid];
}

/**
 * Save string for a language
 * 
 * Locale's cache needs refreshing after calling this one. We don't do it here as
 * locale_refresh_cache() is a quite expensive operation.
 */
function i18nstrings_save_string($strid, $string) {

  // This will store the string sources to update or create at the end
  if ($source = _i18nstrings_get_source($string, $strid)) {
    if (!$source->strid) {

      // We have the string, just need to create i18n row
      $source->strid = $strid;
      $source->paths[] = $strid;
      _i18nstrings_save_source($source);
    }
    elseif ($source->source != $string) {
      if (count($source->paths) == 1) {

        // This is a unique i18n row, just update locale source
        unset($source->strid);

        // So i18n row is not updated we add it back 2 lines below
        $source->source = $string;
        _i18nstrings_save_source($source);
        $source->strid = $strid;
      }
      else {

        // Fuck, there are more i18n rows, we need to update this one and create a new one
        $update = $source;
        $update->paths = array_diff($source->paths, array(
          $strid,
        ));
        unset($source->strid);
        _i18nstrings_save_source($update);

        // Now we save the new one but search before for this other string
        if ($search = _i18nstrings_get_source($string)) {
          $source = $search;
          $source->paths[] = $strid;
        }
        else {

          // String not found so we create a full new one at the end
          unset($source->lid);
          $source->paths = array(
            $strid,
          );
        }
        $source->strid = $strid;
        _i18nstrings_save_source($source);
      }
    }
    else {

      // Just check in case the strid is not in locale table, this shouldn't happen thoug
      if (!in_array($strid, $source->paths)) {
        $source->paths[] = $strid;
        _i18nstrings_save_source($source);
      }
    }
  }
  else {

    // Full create
    $source = new Stdclass();
    $source->strid = $strid;
    $source->source = $string;
    $source->paths[] = $strid;
    _i18nstrings_save_source($source);
  }
  return $source;
}

/**
 * Get source string from locale system
 * 
 * @param $string
 *   String in the default language
 * @param $strid
 *   String id
 */
function _i18nstrings_get_source($string, $strid = NULL) {
  $source = NULL;

  // First try with string id using i18n_locale
  if ($strid) {
    $source = db_fetch_object(db_query("SELECT i.*, s.source, s.location FROM {i18n_locale} i LEFT JOIN {locales_source} s ON i.lid = s.lid WHERE strid = '%s'", $strid));
  }
  if (!$source && $string) {

    // Retry with the string itself using the locales_source table
    $source = db_fetch_object(db_query("SELECT s.* FROM {locales_source} s WHERE s.source = '%s'", $string));
  }
  if ($source) {

    // This will let us know how many object elements use this source string
    // If location is empty (may happen when imported strings, the array should countain one empty string)
    $source->paths = explode(I18NSTRINGS_SPLIT, $source->location);
  }
  return $source;
}

/**
 * Update locale source and i18n table
 */
function _i18nstrings_save_source(&$source, $update = TRUE) {

  // Build location with all the paths this string belongs to
  $source->paths = array_unique($source->paths);
  $source->location = implode(I18NSTRINGS_SPLIT, $source->paths);
  if (!$source->lid) {

    // Create locales_source and get new lid
    db_query("INSERT INTO {locales_source} (location, source) VALUES ('%s', '%s')", $source->location, $source->source);
    $source->lid = db_result(db_query("SELECT lid FROM {locales_source} WHERE location = '%s' AND source = '%s'", $source->location, $source->source));
  }
  elseif ($update) {

    // Update locales source
    db_query("UPDATE {locales_source} SET location = '%s', source = '%s' WHERE lid = %d", $source->location, $source->source, $source->lid);
  }

  // Now update/insert into i18n table
  if ($source->strid) {
    db_query("DELETE FROM {i18n_locale} WHERE strid = '%s'", $source->strid);
    db_query("INSERT INTO {i18n_locale}(strid, lid) VALUES('%s', %d)", $source->strid, $source->lid);
  }
}

/**
 * Translate configurable string, and store for l10n client
 * 
 * @param $strid
 *   Textgroup and location glued with ':'
 *   I.e. profile:
 * @param $default
 *   String in default language. Default language may or may not be English
 * @param $langcode
 *   Optional language code if different from current request language
 * @param $update
 *   Whether to update/create the string
 */
function i18nstrings_tt($strid, $default, $language = NULL, $update = FALSE) {
  global $locale, $l10n_client_strings;

  //dsm("i18nstrings translating $strid $default");
  $language = $language ? $language : $locale;
  $string = NULL;
  if ($update) {
    i18nstrings_save_string($strid, $default);
  }
  if ($language == i18n_default_language()) {

    // We just translate from default language, not to default language
    return $default;
  }
  else {

    // We get the full source object to add it to l10n list, if not existing it will be created, so we get an lid
    $source = i18nstrings_get_string($strid, $language, $default);
    if (!empty($source->translation)) {
      $l10n_client_strings[$default] = $source;
      return $source->translation;
    }
    else {
      $l10n_client_strings[$default] = !empty($source) ? $source : TRUE;
      return $default;
    }
  }
}

Functions

Namesort descending Description
i18nstrings_admin Menu callback. Administration page
i18nstrings_admin_form Form callback: i18nstrings_admin_form
i18nstrings_admin_form_submit Form submit callback
i18nstrings_admin_overview List of strings
i18nstrings_get_string Get string for a language.
i18nstrings_load Load string translations
i18nstrings_menu Implementation of hook_menu().
i18nstrings_save_string Save string for a language
i18nstrings_tt Translate configurable string, and store for l10n client
_i18nstrings_get_source Get source string from locale system
_i18nstrings_save_source Update locale source and i18n table

Constants

Namesort descending Description
I18NSTRINGS_SPLIT