Index: l10n_community/branch.inc
===================================================================
RCS file: l10n_community/branch.inc
diff -N l10n_community/branch.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ l10n_community/branch.inc	20 Oct 2009 17:31:33 -0000
@@ -0,0 +1,188 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ *   Language branch management.
+ */
+
+/**
+ * User interface for the branch management page.
+ */
+function l10n_community_branch_page($langcode = NULL, $bid = NULL) {
+  $languages = l10n_community_get_languages();
+  $breadcrumb = array(
+    l(t('Home'), NULL),
+    l($languages[$langcode]->name, 'translate/languages/'. $langcode),
+  );
+
+  // Set a matching title with the translation page.
+  drupal_set_title(t('Manage @language language branches', array('@language' => $languages[$langcode]->name)));
+
+  // Add missing breadcrumb.
+  drupal_set_breadcrumb($breadcrumb);
+  
+  $header = array(t('Id'), t('Tag'), t('Title'), array('colspan' => '2', 'data' => t('Operations')));
+  $rows[] = array(0, $langcode, t('Base branch'), array('colspan' => 2, data => t('Cannot be edited or deleted')));
+  
+  $branches = l10n_community_get_branches($langcode);
+  foreach ($branches as $branch_id => $branch) {
+    if ($bid == $branch_id) {
+      // Current branch row.
+      $rows[] = array(
+        'data' => array($branch->bid, 'ro-'. $branch->tag, $branch->title, l(t('edit'), 'translate/languages/'. $langcode . '/branch/'. $branch->bid), l(t('delete'), 'translate/languages/'. $langcode . '/branch/'. $branch->bid .'/delete')),
+        'class' => 'l10n-community-branch-active',
+      );
+    }
+    else {
+      // Regular row.
+      $rows[] = array($branch->bid, 'ro-'. $branch->tag, $branch->title, l(t('edit'), 'translate/languages/'. $langcode . '/branch/'. $branch->bid), l(t('delete'), 'translate/languages/'. $langcode . '/branch/'. $branch->bid .'/delete'));
+    }
+  }
+
+  $output = theme('table', $header, $rows);
+  if ($bid) {
+    $output .= '<div class="l10n-branch-add">'. l(t('Add a new branch'), 'translate/languages/'. $langcode . '/branch') .'</div>';
+  }
+  $output .= drupal_get_form('l10n_community_branch_form', ($bid ? $branches[$bid] : NULL), $langcode, $languages[$langcode]->name);
+  return $output;
+}
+
+/**
+ * Branch insert/edit form.
+ */
+function l10n_community_branch_form(&$form_state, $branch, $langcode, $language_name) {
+  $form['bid'] = array(
+    '#type'         =>  'value',
+    '#value'        =>  $branch->bid ? $branch->bid : 0,
+  );
+  $form['language'] = array(
+    '#type'         =>  'value',
+    '#value'        =>  $langcode,
+  );
+  $form['branch'] = array(
+    '#type'         =>  'fieldset',
+    '#title'        =>  $branch->bid ? t('Edit branch <strong>@langcode-@tag</strong>', array('@langcode' => $langcode, '@tag' => $branch->tag)) : t('Add a new branch to @language language', array('@language' => $language_name)),
+  );
+  $form['branch']['tag'] = array(
+    '#type'         =>  'textfield',
+    '#title'        =>  t('Branch tag'),
+    '#description'  =>  t('The branch tag is the computer readable name of this branch and is unique inside this language. Only letters, digit and dashes are allowed. The first character must be a letter. <strong>Do not prefix with the language prefix!</strong>. Leave out "en-", "de-", "it-". This will be done automatically by Localization server.'),
+    '#default_value'=>  $branch->tag,
+    '#maxlength'    =>  50,
+    '#size'         =>  20,
+    '#required'     =>  TRUE,
+  );
+  $form['branch']['title'] = array(
+    '#type'         =>  'textfield',
+    '#title'        =>  t('Branch title'),
+    '#description'  =>  t('Enter a descriptive, human readable title of this branch.'),
+    '#default_value'=>  $branch->title,
+    '#maxlength'    =>  255,
+    '#required'     =>  TRUE,
+  );
+  $form['branch']['description'] = array(
+    '#type'         =>  'textarea',
+    '#title'        =>  t('Branch description'),
+    '#description'  =>  t('Enter a description for this branch. Explain here what was the reason for creating this branch and what are the main differences in comparition with the base branch for this language.'),
+    '#default_value'=>  $branch->description,
+    '#maxlength'    =>  255,
+  );
+  $form['branch']['submit'] = array(
+    '#type'   =>  'submit',
+    '#value'  =>  $branch->bid ? t('Save') : t('Add'),
+  );
+
+  return $form;
+}
+
+/**
+ * Branch form validation callback.
+ */
+function l10n_community_branch_form_validate($form, &$form_state) {
+  // Check the tag pattern.
+  if (!preg_match('/^[a-z]([a-z0-9\-]*)$/i', $form_state['values']['tag'])) {
+    form_set_error('tag', t('You have entered an invalid tag for this branch (@tag). The branch tag should start with  a letter and should contain only letters, digits and dashes.', array('@tag' => $form_state['values']['tag'])));
+  }
+  
+  // Check the for tag duplicate in this language.
+  if ($branch = db_fetch_object(db_query("SELECT bid, tag, title FROM {l10n_community_branch} WHERE language = '%s' AND tag = '%s' AND bid <> %d", $form_state['values']['language'], $form_state['values']['tag'], $form_state['values']['bid']))) {
+    form_set_error('tag', t('A branch with the tag <strong>@tag</strong> already exist for this language. You cannot use the same tag twice inside a language. If you want to edit the existing tag visit: !link', array('@tag' => $form_state['values']['tag'], '!link' => l($form_state['values']['language'] .'-'. $branch->tag .': '. $branch->title, 'translate/languages/'. $form_state['values']['language'] . '/branch/'. $branch->bid))));
+  }
+}
+
+/**
+ * Branch form submit callback.
+ */
+function l10n_community_branch_form_submit($form, &$form_state) {
+  $values = $form_state['values'];
+  if ($values['bid']) {
+    if (db_query("UPDATE {l10n_community_branch} SET tag = '%s', language = '%s', title = '%s', description = '%s' WHERE bid = %d", $values['tag'], $values['language'], $values['title'], $values['description'], $values['bid'])) {
+      drupal_set_message(t('Branch: <strong>@langcode-@tag</strong> was successfully updated.', array('@langcode' => $values['language'], '@tag' => $values['tag'])));
+    }
+  }
+  else {
+    if (db_query("INSERT INTO {l10n_community_branch} (tag, language, title, description) VALUES ('%s', '%s', '%s', '%s')", $values['tag'], $values['language'], $values['title'], $values['description'])) {
+      drupal_set_message(t('A new branch: <strong>@langcode-@tag</strong> was successfully added.', array('@langcode' => $values['language'], '@tag' => $values['tag'])));
+    }
+  }
+}
+
+/**
+ * Branch deleting confirmation page.
+ */
+function l10n_community_branch_delete(&$form_state, $langcode = NULL, $bid = NULL) {
+  $languages = l10n_community_get_languages();
+  $branch = l10n_community_get_branches($langcode, $bid);
+
+  $form['#redirect'] = 'translate/languages/'. $langcode . '/branch';
+  $form['bid'] = array(
+    '#type' =>  'value',
+    '#value' => $bid,
+  );
+  $form['tag'] = array(
+    '#type' =>  'value',
+    '#value' => $branch->tag,
+  );
+  $form['langcode'] = array(
+    '#type' =>  'value',
+    '#value' => $langcode,
+  );
+
+  $question = t('Are you sure you want to delete branch @tag from the @language_name language?', array('@tag' => $langcode .'-'. $branch->tag, '@language_name' => $languages[$langcode]->name));
+  
+  $items = array(
+    t('Id: @id', array('@id' => $branch->bid)),
+    t('Tag: @tag', array('@tag' => $langcode .'-'. $branch->tag)),
+    t('Language: @language', array('@language' => $languages[$langcode]->name)),
+    t('Title: @title', array('@title' => $branch->title)),
+    t('Description: @description', array('@description' => $branch->description)),
+  );
+  $description = theme('item_list', $items, t('Branch details'));
+
+  $translations = db_result(db_query("SELECT COUNT(*) FROM {l10n_community_translation} WHERE language = '%s' AND bid = %d AND is_suggestion = 0", $langcode, $bid));
+  $suggestions = db_result(db_query("SELECT COUNT(*) FROM {l10n_community_translation} WHERE language = '%s' AND bid = %d AND is_suggestion = 1", $langcode, $bid));
+  if ($translations || $suggestions) {
+    $items = array();
+    if ($translations) {
+      $items[] = format_plural($translations, '1 translation', '@count translations', array('@count', $translations));
+    }
+    if ($suggestions) {
+      $items[] = format_plural($suggestions, '1 suggestion', '@count suggestions', array('@count', $suggestions));
+    }
+    $description .= theme('item_list', $items, t('Warning! Translation data attached!'));
+    $description .= '<p>'. t('By deleting this branch you will delete also all the translations and suggestions belonging to this branch') . '</p>';
+  }
+  $description .= '<p>'. t('This action cannot be undone.') .'</p>';
+  
+  return confirm_form($form, $question, 'translate/languages/'. $langcode . '/branch', $description, t('Delete'), t('Cancel'));
+}
+
+/**
+ * Branch delete submit callback.
+ */
+function l10n_community_branch_delete_submit($form, &$form_state) {
+  db_query("DELETE FROM {l10n_community_branch} WHERE bid = %d", $form_state['values']['bid']);
+  db_query("DELETE FROM {l10n_community_translation} WHERE bid = %d", $form_state['values']['bid']);
+  drupal_set_message(t('The branch @tag has been deleted together with all related translation data.', array('@tag' => $form_state['values']['langcode'] .'-'. $form_state['values']['tag'])));
+}
Index: l10n_community/export.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/l10n_server/l10n_community/Attic/export.inc,v
retrieving revision 1.1.2.15.2.15
diff -u -p -r1.1.2.15.2.15 export.inc
--- l10n_community/export.inc	18 Sep 2009 18:03:18 -0000	1.1.2.15.2.15
+++ l10n_community/export.inc	20 Oct 2009 17:31:33 -0000
@@ -85,6 +85,18 @@ function l10n_community_export_form(&$fo
     // Set this project as default value if identified.
     $form['data']['project']['#default_value'] = $projects[$uri]->title;
 
+    $branch_options = l10n_community_get_branches_options($langcode);
+    $form['date']['bid'] = array(
+      '#type' => 'select',
+      '#title' => t('Language branch'),
+      '#description' => t('Select the language branch that you want to use as source for this export.'),
+      '#options' => $branch_options,
+      // TODO: Add a default from passed filters when http://drupal.org/node/554144.
+      // '#default_value' => 
+      '#required' => TRUE,
+      '#disabled' => (count($branch_options) == 1),
+    );
+    
     $releases = l10n_community_get_releases($uri);
     $release_options = array('all' => t('All'));
     foreach ($releases as $rid => $this_release) {
@@ -189,6 +201,7 @@ function l10n_community_export_form_subm
     $uri,
     ($form_state['values']['release'] == 'all' ? NULL : $form_state['values']['release']),
     $language,
+    $form_state['values']['bid'],
     ($type != 'translation'),
     // If not set (exporting a template for a module), stick to all-in-one.
     isset($form_state['values']['version']) ? $form_state['values']['version'] : 'all-in-one',
@@ -366,6 +379,8 @@ function _l10n_community_export_string_f
  *   with all releases considered.
  * @param $language
  *   Language object.
+ * @param $bid
+ *   Branch id.
  * @param $template
  *   TRUE if templates should be exported, FALSE if translations.
  * @param $version
@@ -380,7 +395,7 @@ function _l10n_community_export_string_f
  * @todo
  *   Look into possibly exporting suggestions as fuzzy translations.
  */
-function l10n_community_export($uri, $release = NULL, $language = NULL, $template = TRUE, $version = NULL, $compact = FALSE) {
+function l10n_community_export($uri, $release = NULL, $language = NULL, $bid = NULL, $template = TRUE, $version = NULL, $compact = FALSE) {
   // l10n_community_requirements() makes sure there is a status
   // error if this is not installed.
   include_once 'Archive/Tar.php';
@@ -393,8 +408,8 @@ function l10n_community_export($uri, $re
   }
   else {
     // We only export active translations, not suggestions.
-    $sql = "SELECT s.sid, s.value, s.context, f.location, f.revision, l.lineno, l.type, t.translation, t.uid_approved, t.time_approved FROM {l10n_community_file} f INNER JOIN {l10n_community_line} l ON f.fid = l.fid INNER JOIN {l10n_community_string} s ON l.sid = s.sid  LEFT JOIN {l10n_community_translation} t ON s.sid = t.sid AND t.language = '%s' AND is_active = 1 AND is_suggestion = 0 WHERE f.pid = %d";
-    $sql_args = array($language->language, $project->pid);
+    $sql = "SELECT s.sid, s.value, s.context, f.location, f.revision, l.lineno, l.type, t.translation, t.bid, t.uid_approved, t.time_approved FROM {l10n_community_file} f INNER JOIN {l10n_community_line} l ON f.fid = l.fid INNER JOIN {l10n_community_string} s ON l.sid = s.sid  LEFT JOIN {l10n_community_translation} t ON s.sid = t.sid AND t.language = '%s' AND is_active = 1 AND is_suggestion = 0 WHERE f.pid = %d AND t.bid IN(%d, 0) GROUP BY s.sid";
+    $sql_args = array($language->language, $project->pid, $bid);
   }
 
   if (isset($release)) {
@@ -407,7 +422,8 @@ function l10n_community_export($uri, $re
 
   // Source strings will be repeated as many times as they appear, so to generate
   // the export file properly, order by the source id.
-  $sql .= ' ORDER BY s.sid';
+  // Branch id is added so that branches ($bid > 0) will override base ($bid == 0) on GROUP BY
+  $sql .= ' ORDER BY s.sid ASC, t.bid ASC';
 
   $result = db_query($sql, $sql_args);
   $previous_sid = 0;
@@ -535,13 +551,15 @@ function l10n_community_export($uri, $re
     }
   }
 
+  $branch = l10n_community_get_branches(NULL, $bid);
+  $branch_tag = $branch ? '-'. $branch->tag : '';
   if ($version == 'all-in-one') {
     // Output a single PO(T) file in this case.
-    return array('text/plain', $tempfile, $uri .'-'. (isset($release) ? $release->title : 'all') . (isset($language) ? '-'. $language->language : '') . ($template ? '.pot' : '.po'));
+    return array('text/plain', $tempfile, $uri .'-'. (isset($release) ? $release->title : 'all') . (isset($language) ? '-'. $language->language . $branch_tag : '') . ($template ? '.pot' : '.po'));
   }
   else {
     // Output a package in this case.
-    return array('application/x-compressed', $tempfile, $uri .'-'. (isset($release) ? $release->title : 'all') . (isset($language) ? '-'. $language->language : '') . ($template ? '-templates' : '-translations') .'.tgz');
+    return array('application/x-compressed', $tempfile, $uri .'-'. (isset($release) ? $release->title : 'all') . (isset($language) ? '-'. $language->language . $branch_tag : '') . ($template ? '-templates' : '-translations') .'.tgz');
   }
 }
 
Index: l10n_community/import.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/l10n_server/l10n_community/Attic/import.inc,v
retrieving revision 1.1.2.5.2.13
diff -u -p -r1.1.2.5.2.13 import.inc
--- l10n_community/import.inc	8 Oct 2009 07:17:45 -0000	1.1.2.5.2.13
+++ l10n_community/import.inc	20 Oct 2009 17:31:34 -0000
@@ -43,6 +43,17 @@ function l10n_community_import_form($for
     '#size' => 50,
     '#description' => t('A Gettext Portable Object (.po) file to upload.'),
   );
+  $branch_options = l10n_community_get_branches_options($langcode);
+  $form['bid'] = array(
+    '#type' => 'select',
+    '#title' => t('Language branch'),
+    '#description' => t('Select the destination branch into which the imported strings will be inserted. If unsure leave the default value that is the base branch.'),
+    '#options' => $branch_options,
+    // TODO: Add a default from passed filters when http://drupal.org/node/554144.
+    // '#default_value' => 
+    '#required' => TRUE,
+    '#disabled' => (count($branch_options) == 1),
+  );
   $form['is_suggestion'] = array(
     '#title' => t('Store as suggestions'),
     '#type' => 'checkbox',
@@ -95,7 +106,7 @@ function l10n_community_import_form_subm
     }
 
     // Do the actual parsing on the local file.
-    if (l10n_community_import($file, $form_state['values']['langcode'], $form_state['values']['is_suggestion'], $uid)) {
+    if (l10n_community_import($file, $form_state['values']['langcode'], $form_state['values']['bid'], $form_state['values']['is_suggestion'], $uid)) {
       // Get status report on what was done in the process.
       list($inserted, $updated, $unchanged, $suggested, $duplicates) = _l10n_community_import_one_string();
       drupal_set_message(t('The translation was successfully imported.'));
@@ -122,12 +133,14 @@ function l10n_community_import_form_subm
  *   Drupal file object corresponding to the PO file to import.
  * @param $langcode
  *   Language code.
+ * @param $bid
+ *   Branch id.
  * @param $is_suggestion
  *   Strings uploaded are to be saved as suggestions (TRUE or FALSE).
  * @param $uid
  *   User id used to save attribution information.
  */
-function l10n_community_import($file, $langcode, $is_suggestion, $uid) {
+function l10n_community_import($file, $langcode, $bid, $is_suggestion, $uid) {
   include_once 'includes/locale.inc';
 
   $fd = fopen($file->filepath, "rb"); // File will get closed by PHP on return
@@ -155,7 +168,7 @@ function l10n_community_import($file, $l
         $current["#"][] = substr($line, 1);
       }
       elseif (($context == "MSGSTR") || ($context == "MSGSTR_ARR")) { // End current entry, start a new one
-        _l10n_community_import_one_string($current, $langcode, $is_suggestion, $uid);
+        _l10n_community_import_one_string($current, $langcode, $bid, $is_suggestion, $uid);
         $current = array();
         $current["#"][] = substr($line, 1);
         $context = "COMMENT";
@@ -181,7 +194,7 @@ function l10n_community_import($file, $l
     }
     elseif (!strncmp("msgid", $line, 5)) {
       if ($context == "MSGSTR") {   // End current entry, start a new one
-        _l10n_community_import_one_string($current, $langcode, $is_suggestion, $uid);
+        _l10n_community_import_one_string($current, $langcode, $bid, $is_suggestion, $uid);
         $current = array();
       }
       elseif ($context == "MSGID") { // Already in this context? Parse error
@@ -199,7 +212,7 @@ function l10n_community_import($file, $l
     }
     elseif (!strncmp("msgctxt", $line, 7)) {
       if ($context == "MSGSTR") {   // End current entry, start a new one
-        _l10n_community_import_one_string($current, $langcode, $is_suggestion, $uid);
+        _l10n_community_import_one_string($current, $langcode, $bid, $is_suggestion, $uid);
         $current = array();
       }
       elseif (!empty($current["msgctxt"])) { // Already in this context? Parse error
@@ -276,7 +289,7 @@ function l10n_community_import($file, $l
 
   // End of PO file, flush last entry
   if (!empty($current) && !empty($current['msgstr'])) {
-    _l10n_community_import_one_string($current, $langcode, $is_suggestion, $uid);
+    _l10n_community_import_one_string($current, $langcode, $bid, $is_suggestion, $uid);
   }
   elseif ($context != "COMMENT") {
     _locale_import_message('The translation file %filename ended unexpectedly at line %line.', $file, $lineno);
@@ -293,12 +306,14 @@ function l10n_community_import($file, $l
  *   Details of the string stored.
  * @param $langcode
  *   Language to store the string in.
+ * @param $bid
+ *   Branch id.
  * @param $is_suggestion
  *   TRUE if the string to store is a suggestion, FALSE otherwise.
  * @param $uid
  *   User id used to save attribution information.
  */
-function _l10n_community_import_one_string($value = NULL, $langcode = NULL, $is_suggestion = FALSE, $uid = NULL) {
+function _l10n_community_import_one_string($value = NULL, $langcode = NULL, $bid = NULL, $is_suggestion = FALSE, $uid = NULL) {
   global $user;
 
   static $inserted = 0;
@@ -337,12 +352,12 @@ function _l10n_community_import_one_stri
     // strings like 'operations' and 'Operations'.
     if ($sid = db_result(db_query("SELECT sid FROM {l10n_community_string} WHERE BINARY value = '%s' AND context = '%s'", $value['msgid'], $value['msgctxt']))) {
       // We have this source string (otherwise we don't save anything).
-      $translation = db_fetch_object(db_query("SELECT translation FROM {l10n_community_translation} WHERE sid = %d AND language = '%s' AND is_suggestion = 0 AND is_active = 1", $sid, $langcode));
+      $translation = db_fetch_object(db_query("SELECT translation FROM {l10n_community_translation} WHERE sid = %d AND language = '%s' AND bid = %d AND is_suggestion = 0 AND is_active = 1", $sid, $langcode, $bid));
 
       // Merge plural versions into one for saving values.
       $value['msgstr'] = is_array($value['msgstr']) ? join("\0", $value['msgstr']) : $value['msgstr'];
       
-      // Trim imported translation to have whitesapce from source string.
+      // Trim imported translation to have whitespace from source string.
       $value['msgstr'] = l10n_community_trim($value['msgstr'], $value['msgid']);
 
       if ($translation && (empty($translation->translation) || ($translation->translation != $value['msgstr']))) {
@@ -355,7 +370,7 @@ function _l10n_community_import_one_stri
 
       if ($is_suggestion || !$translation) {
         if (!l10n_community_is_duplicate($value['msgstr'], $sid, $langcode)) {
-          l10n_community_target_save($sid, $value['msgstr'], $langcode, $uid, $is_suggestion, $inserted, $updated, $unchanged, $suggested);
+          l10n_community_target_save($sid, $value['msgstr'], $langcode, $bid, $uid, $is_suggestion, $inserted, $updated, $unchanged, $suggested);
         }
         else {
           $duplicates++;
Index: l10n_community/l10n_community.css
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/l10n_server/l10n_community/Attic/l10n_community.css,v
retrieving revision 1.1.2.10.2.6
diff -u -p -r1.1.2.10.2.6 l10n_community.css
--- l10n_community/l10n_community.css	10 Sep 2009 09:15:03 -0000	1.1.2.10.2.6
+++ l10n_community/l10n_community.css	20 Oct 2009 17:31:34 -0000
@@ -206,3 +206,9 @@ ul.l10n-community-strings li .buttons {
 .l10n-community-string .original {
   display:none;
 }
+
+tr.l10n-community-branch-active td {
+  font-weight:bold;
+  color: #000000;
+  background-color: #efefef;
+}
\ No newline at end of file
Index: l10n_community/l10n_community.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/l10n_server/l10n_community/Attic/l10n_community.install,v
retrieving revision 1.1.2.11.2.12
diff -u -p -r1.1.2.11.2.12 l10n_community.install
--- l10n_community/l10n_community.install	18 Sep 2009 18:03:19 -0000	1.1.2.11.2.12
+++ l10n_community/l10n_community.install	20 Oct 2009 17:31:34 -0000
@@ -290,6 +290,13 @@ function l10n_community_schema() {
         'length' => '12',
         'not null' => TRUE
       ),
+      'bid' => array(
+        'description' => 'Reference to the {l10n_community_branch}.bid to which the string is being translated or "0" for base branch.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'disp-width' => '11'
+      ),
       'translation' => array(
         'description' => 'The actual translation or suggestion.',
         'type' => 'text',
@@ -353,10 +360,51 @@ function l10n_community_schema() {
       'suggestion_active' => array('is_suggestion', 'is_active'),
       'uid_entered' => array('uid_entered'),
       'sid_language_suggestion' => array('sid', 'language', 'is_suggestion'),
-      'sid' => array('sid')
+      'sid' => array('sid'),
+      'bid' => array('bid'),
     ),
   );
 
+  $schema['l10n_community_branch'] = array(
+    'fields' => array(
+      'bid' => array(
+        'description' => 'Internal numeric identifier for a branch.',
+        'type' => 'serial',
+        'not null' => TRUE,
+        'disp-width' => '11'
+      ),
+      'language' => array(
+        'description' => 'Reference to the {languages}.language to which the string is being translated.',
+        'type' => 'varchar',
+        'length' => '12',
+        'not null' => TRUE,
+      ),
+      'tag' => array(
+        'description' => 'The per-language unique string identifier. Must contain only letters, digits, dashes and must start with a letter.',
+        'type' => 'varchar',
+        'length' => '50',
+        'default' => '',
+        'not null' => TRUE,
+      ),
+      'title' => array(
+        'description' => 'The human readable name of the branch.',
+        'type' => 'varchar',
+        'length' => '255',
+        'default' => '',
+        'not null' => TRUE,
+      ),
+      'description' => array(
+        'description' => 'A brief description of a branch.',
+        'type' => 'text',
+        'default' => '',
+        'not null' => FALSE,
+      ),
+    ),
+    'primary key' => array('bid'),
+    'unique keys' => array(
+      'branch' => array('language', 'tag')
+    ),
+  );
   return $schema;
 }
 
@@ -589,3 +637,20 @@ function l10n_community_update_6007() {
   }
   return $ret;
 }
+
+/**
+ * Add branch table and corresponding index.
+ */
+function l10n_community_update_6008() {
+  $ret = array();
+  $schema = l10n_community_schema();
+  
+  // Add branch field and related index to l10n_community_translation table
+  db_add_field($ret, 'l10n_community_translation', 'bid', $schema['l10n_community_translation']['fields']['bid']);
+  db_add_index($ret, 'l10n_community_translation', 'bid', array('bid'));
+  
+  // Add the branch table
+  db_create_table($ret, 'l10n_community_branch', $schema['l10n_community_branch']);
+  
+  return $ret;
+}
Index: l10n_community/l10n_community.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/l10n_server/l10n_community/Attic/l10n_community.module,v
retrieving revision 1.1.2.23.2.57
diff -u -p -r1.1.2.23.2.57 l10n_community.module
--- l10n_community/l10n_community.module	7 Oct 2009 18:21:36 -0000	1.1.2.23.2.57
+++ l10n_community/l10n_community.module	20 Oct 2009 17:31:34 -0000
@@ -269,6 +269,35 @@ function l10n_community_menu() {
     'type' => MENU_LOCAL_TASK,
     'weight' => 0,
   );
+  $items['translate/languages/%l10n_community_language/branch'] = array(
+    'title' => 'Branches',
+    'page callback' => 'l10n_community_branch_page',
+    'page arguments' => array(2),
+    'file' => 'branch.inc',
+    'access callback' => 'l10n_community_branch_access',
+    'type' => MENU_LOCAL_TASK,
+    'weight' => 2,
+  );
+  $items['translate/languages/%l10n_community_language/branch/%l10n_community_branch'] = array(
+    'title' => 'Branches',
+    'page callback' => 'l10n_community_branch_page',
+    'page arguments' => array(2, 4),
+    'load arguments' => array(2),
+    'file' => 'branch.inc',
+    'access callback' => 'l10n_community_branch_access',
+    'type' => MENU_LOCAL_TASK,
+    'weight' => 2,
+  );
+  $items['translate/languages/%l10n_community_language/branch/%l10n_community_branch/delete'] = array(
+    'title' => 'Branches',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('l10n_community_branch_delete', 2, 4),
+    'load arguments' => array(2),
+    'file' => 'branch.inc',
+    'access callback' => 'l10n_community_branch_access',
+    'type' => MENU_LOCAL_TASK,
+    'weight' => 2,
+  );
 
   // We have a valid project name from the web address.
   $items['translate/projects/%l10n_community_project'] = array(
@@ -359,6 +388,13 @@ function l10n_community_project_admin_lo
 }
 
 /**
+ * Menu loader function for %l10n_community_branch to validate language branch.
+ */
+function l10n_community_branch_load($bid = NULL, $langcode) {
+  return l10n_community_get_branches($langcode, $bid) ? $bid : FALSE;
+}
+
+/**
  * Access callback for editing and import paths. Requires edit privileges for given language.
  *
  * @param $langcode
@@ -378,6 +414,14 @@ function l10n_community_export_access() 
 }
 
 /**
+ * Access callback for managing branches.
+ */
+function l10n_community_branch_access() {
+  // TODO: Check again this...
+  return user_access('administer localization groups') || user_access('edit any localization group'); 
+}
+
+/**
  * Access to the moderation screen.
  */
 function l10n_community_moderation_access($langcode) {
@@ -934,6 +978,74 @@ function l10n_community_get_languages($k
 }
 
 /**
+ * Helper function for branch listing.
+ *
+ * @param $langcode
+ *   Language code for filtering branches. When present only branches for this language will be returned.
+ * @param $bid
+ *   Branch ID. When present only this branch will be returned.
+ * @return
+ *   If $langcode and $bid are null all the branches will be returned. If only $bid is null all the branches belonging to a language are returned. If $bid is passed that specific branch will be returned.
+ */
+function l10n_community_get_branches($langcode = NULL, $bid = NULL) {
+  static $branches;
+
+  if (!isset($branches)) {
+    $result = db_query("SELECT * FROM {l10n_community_branch} ORDER BY bid ASC");
+    $branches = array();
+    while ($branch = db_fetch_object($result)) {
+      $branches[$branch->bid] = $branch;
+    }
+  }
+
+  // An invalid $bid was supplied.
+  if ($bid && !isset($branches[$bid])) {
+    return FALSE;
+  }
+  
+  // A branch ID was supplied. No language.
+  if (($bid && isset($branches[$bid])) && is_null($langcode)) {
+    return $branches[$bid];
+  }
+  // A branch ID and a language were supplied. Check if they match, FALSE otherwise
+  elseif ($bid && isset($branches[$bid]) && (!empty($langcode))) {
+    return ($branches[$bid]->language == $langcode) ? $branches[$bid] : FALSE;
+  }
+  
+  // A language was supplied but no $bid. Return all branches for that language.
+  if (!empty($langcode) && is_null($bid)) {
+    $return = array();
+    foreach ($branches as $branch_id => $branch) {
+      if ($branch->language == $langcode)
+      $return[$branch_id] = $branch;
+    }
+    return $return;
+  }
+  
+  // No arguments were passed. Retun the whole list.
+  if (is_null($bid) && is_null($langcode)) {
+    return $branches;
+  }
+}
+
+/**
+ * Get a FAPI select options array from branches belonging to a specific language.
+ *
+ * @param $langcode
+ *   Language code, for example 'hu', 'pt-br', 'de' or 'it'.
+ * @return
+ *   Associative array with the list of branches from a language, including the base branch. Mostly used in FAPI selects.
+ */
+function l10n_community_get_branches_options($langcode) {
+  $branches = l10n_community_get_branches($langcode);
+  $branch_options = array('0' => $langcode .': '. t('Base branch'));
+  foreach ($branches as $branch_id => $branch) {
+    $branch_options[$branch_id] = $langcode .'-'. $branch->tag .': '. $branch->title;
+  }
+  return $branch_options;
+}
+ 
+/**
  * Get translation permission level for a specific user.
  *
  *
@@ -1108,6 +1220,8 @@ function l10n_community_get_contexts() {
  *   The translation string.
  * @param $langcode
  *   Language code, for example: 'hu', 'pt-br', 'de', 'it' and so on.
+ * @param $bid
+ *   Branch id.
  * @param $uid
  *   User ID.
  * @param $suggestion
@@ -1126,10 +1240,10 @@ function l10n_community_get_contexts() {
  *   was an existing active suggestion, in which case a new suggestion
  *   is added. This is an edge case, but possibly not desired.
  */
-function l10n_community_target_save($sid, $translation, $langcode, $uid, $suggestion, &$inserted, &$updated, &$unchanged, &$suggested) {
+function l10n_community_target_save($sid, $translation, $langcode, $bid, $uid, $suggestion, &$inserted, &$updated, &$unchanged, &$suggested) {
 
   // Look for an existing active translation, if any.
-  $existing_string = db_fetch_object(db_query("SELECT sid, tid, translation FROM {l10n_community_translation} WHERE sid = %d AND language = '%s' AND is_suggestion = 0 AND is_active = 1", $sid, $langcode));
+  $existing_string = db_fetch_object(db_query("SELECT sid, tid, translation FROM {l10n_community_translation} WHERE sid = %d AND language = '%s' AND bid = %d AND is_suggestion = 0 AND is_active = 1", $sid, $langcode, $bid));
 
   if (!empty($existing_string->sid)) {
 
@@ -1143,10 +1257,10 @@ function l10n_community_target_save($sid
       }
       else {
         // Saving a different translation -> deactivate previous translations and suggestions.
-        db_query("UPDATE {l10n_community_translation} SET is_active = 0 WHERE sid = %d AND language = '%s';", $sid, $langcode);
+        db_query("UPDATE {l10n_community_translation} SET is_active = 0 WHERE sid = %d AND language = '%s' AND bid = %d;", $sid, $langcode, $bid);
         $updated++;
       }
-      db_query("INSERT INTO {l10n_community_translation} (sid, translation, language, uid_entered, time_entered, uid_approved, time_approved, is_suggestion, is_active) VALUES (%d, '%s', '%s', %d, %d, %d, %d, %d, 1)", $sid, $translation, $langcode, $uid, time(), ($suggestion ? 0 : $uid), ($suggestion ? 0 : time()), $suggestion);
+      db_query("INSERT INTO {l10n_community_translation} (sid, translation, language, bid, uid_entered, time_entered, uid_approved, time_approved, is_suggestion, is_active) VALUES (%d, '%s', '%s', %d, %d, %d, %d, %d, %d, 1)", $sid, $translation, $langcode, $bid, $uid, time(), ($suggestion ? 0 : $uid), ($suggestion ? 0 : time()), $suggestion);
     }
     else {
       // Same string as existing translation.
@@ -1159,13 +1273,13 @@ function l10n_community_target_save($sid
     if ($suggestion) {
       // No translation yet -> INSERT empty placeholder so we can track
       // suggestions. We track and exclude these by translation = '' later.
-      db_query("INSERT INTO {l10n_community_translation} (sid, translation, language, uid_entered, time_entered, has_suggestion, is_active) VALUES (%d, '', '%s', 0, %d, 1, 1)", $sid, $langcode, time());
-      db_query("INSERT INTO {l10n_community_translation} (sid, language, translation, uid_entered, time_entered, is_suggestion, is_active) VALUES (%d, '%s', '%s', %d, %d, 1, 1)", $sid, $langcode, $translation, $uid, time());
+      db_query("INSERT INTO {l10n_community_translation} (sid, translation, language, bid, uid_entered, time_entered, has_suggestion, is_active) VALUES (%d, '', '%s', %d, 0, %d, 1, 1)", $sid, $langcode, $bid, time());
+      db_query("INSERT INTO {l10n_community_translation} (sid, language, bid, translation, uid_entered, time_entered, is_suggestion, is_active) VALUES (%d, '%s', %d, '%s', %d, %d, 1, 1)", $sid, $langcode, $bid, $translation, $uid, time());
       $suggested++;
     }
     else {
       // No active translation yet -> INSERT.
-      db_query("INSERT INTO {l10n_community_translation} (sid, translation, language, uid_entered, time_entered, is_active) VALUES (%d, '%s', '%s', %d, %d, 1)", $sid, $translation, $langcode, $uid, time());
+      db_query("INSERT INTO {l10n_community_translation} (sid, translation, language, bid, uid_entered, time_entered, is_active) VALUES (%d, '%s', '%s', %d, %d, %d, 1)", $sid, $translation, $langcode, $bid, $uid, time());
       $inserted++;
     }
   }
