Commit 3d7d601c authored by Riccardo Padovani's avatar Riccardo Padovani

Added l10n_update module

parent c0073e0c
This diff is collapsed.
Localization Update
-------------------
Automatically download and update your translations by fetching them from
http://localize.drupal.org or any other Localization server.
The l10n update module helps to keep the translation of your drupal core and
contributed modules up to date with the central Drupal translation repository
at http://localize.drupal.org. Alternatively locally stored translation files
can be used as translation source too.
By choice updates are performed automatically or manually. Locally altered
translations can either be respected or ignored.
The l10n update module is developed for:
* Distributions which include their own translations in .po files.
* Site admins who want to update the translation with each new module revision.
* Site builders who want an easy tool to download translations for a site.
* Multi-sites that share one translation source.
Project page: http://drupal.org/project/l10n_update
Support queue: http://drupal.org/project/issues/l10n_update
Installation
------------
Download, unpack the module the usual way.
Enable this module and the Locale module (core).
You need at least one language other than English.
On Administration > Configuration > Regional and language:
Click "Add language"
Pull-down menu: Choose your new language
Then click "Add language"
Drupal is now importing interface translations. This can take a few minutes.
When it's finished, you'll get a confirmation with a summary of all
translation files that have been pulled in.
If required, enable the new language as default language.
Home > Administration > Configuration > Regional and language:
Select your new language as default
Update interface translations
-----------------------------
On Home > Administration > Configuration > Regional and language:
Choose the "Translation updates" tab
Change "Check for updates" to Daily or Weekly instead of the default "Never".
Cron will from now on check for updated translations, and will report the
update status on the status page (Home > Administration > Reports).
To check the translation status and execute updates manually, go to
Administration > Configuration > Regional and language > Translate inteface
Here you see English and your new language.
Choose the "Update" tab
You see a list of all modules and their translation status.
On the bottom of the page, you can manually update using "Update translations".
Use Drush
---------
You can also use drush to update your translations:
drush l10n-update # Update translations.
drush l10n-update-refresh # Refresh available information.
drush l10n-update-status # Show translation status of available project
Summary of administrative pages
-------------------------------
Translations status overview can be found at
Administration > Configuration > Regional and language > Languages > Translation updates
Update configuration settings can be found at
Administration > Configuration > Regional and language > Translate interface > Update
Translating Drupal core, modules and themes
-------------------------------------------
When Drupal core or contributed modules or themes get installed, Drupal core
checks if .po translation files are present and updates the translations with
the strings found in these files. After this, the localization update module
checks the localization server for more recent translations, and updates
the site translations if a more recent version was found.
Note that the translations contained in the project packages may become
obsolete in future releases.
Changes to translations made locally using the site's build in translation
interface (Administer > Site building > Translate interface > Search) and
changes made using the localization client module are marked. Using the
'Update mode' setting 'Edited translations are kept...', locally edited
strings will not be overwritten by translation updates.
NOTE: Only manual changes made AFTER installing Localization Update module
are preserved. To preserve manual changes made prior to installation of
Localization Update module, use the option 'All existing translations are kept...'.
po files, multi site and distributions
--------------------------------------
Multi sites and other installations that share the file system can share
downloaded translation files. The Localization Update module can save these
translations to disk. Other installations can use these saved translations
as their translation source.
All installations that share the same translation files must be configured
with the same 'Store downloaded files' file path e.g. 'sites/all/translations'.
Set the 'Update source' of one installation to 'Local files and remote server'
or 'Remote server only', all other installations are set to
'Local files only' or 'Local files and remote server'.
Translation files are saved with the following file name syntax:
<module name>-<release>.<language code>.po
For example:
masquerade-6.x-1.5.de.po
tac_lite-7.x-1.0-beta1.nl.po
Po files included in distributions should match this syntax too.
Alternative sources of translation
----------------------------------
Each project i.e. modules, themes, etc. can define alternative translation
servers to retrieve the translation updates from.
Include the following definition in the projects .info file:
l10n server = example.com
l10n url = http://example.com/files/translations/l10n_server.xml
The download path pattern is normally defined in the above defined xml file.
You may override this path by adding a third definition in the .info file:
l10n path = http://example.com/files/translations/%core/%project/%project-%release.%language.po
API
---
Using hook_l10n_servers the l10n update module can be extended to use other
translation repositories. Which is usefull for organisations who maintain
their own translation.
Using hook_l10n_update_projects_alter modules can alter or specify the
translation repositories on a per module basis.
See l10n_update.api.php for more information.
Maintainers
-----------
Jose Reyero
Gábor Hojtsy
Erik Stielstra
/* Expand/collapse image for project title */
html.js .l10n-update-wrapper .project-legend {
padding-right: 10px;
}
html.js .l10n-update-wrapper.collapsed .project-legend {
background: url("../images/menu-collapsed-rtl.png") right 50% no-repeat;
}
html.js .l10n-update-wrapper .project-legend a {
margin-right: -10px;
padding-right: 10px;
}
/* Translation update status data */
html.js .l10n-update-wrapper.collapsed .project .version-status {
float: left;
}
.l10n-update .version-status {
float: left;
}
.l10n-update .version-links {
float: left;
}
html.js .l10n-update-wrapper.collapsed .fieldset-wrapper {
display: none;
}
.l10n-update-wrapper .project .version-status {
display: none;
}
/* Expand/collapse image for project title */
html.js .l10n-update-wrapper .project-title {
background: url("../images/menu-expanded.png") left 65% no-repeat;
padding-left: 10px;
}
html.js .l10n-update-wrapper.collapsed .project-title {
background: url("../images/menu-collapsed.png") left 50% no-repeat;
}
html.js .l10n-update-wrapper .project-title a {
margin-left: -10px;
padding-left: 10px;
}
/* Translation update status data */
html.js .l10n-update-wrapper.collapsed .project .version-status {
display: inline;
float: right;
}
.l10n-update .project-server {
margin: 0 10px;
font-size: 90%;
font-weight: normal
}
.l10n-update .version-status {
float: right;
font-size: 90%;
font-weight: normal;
}
.l10n-update .version-links {
float: right;
padding-right: 1em;
}
(function ($) {
Drupal.behaviors.l10nUpdateCollapse = {
attach: function (context, settings) {
$('.l10n-update .l10n-update-wrapper', context).once('l10nupdatecollapse', function () {
var wrapper = $(this);
// Turn the project title into a clickable link.
// Add an event to toggle the content visibiltiy.
var $legend = $('.project-title', this);
var $link = $('<a href="#"></a>')
.prepend($legend.contents())
.appendTo($legend)
.click(function () {
Drupal.toggleFieldset(wrapper);
return false;
});
});
}
};
})(jQuery);
This diff is collapsed.
<?php
/**
* @file
* API documentation for Localize updater module.
*/
/**
* Returns available translation servers and server definitions.
*
* @return keyed array of available servers.
* Example: array('localize.drupal.org' => array(
* 'name' => 'localize.drupal.org',
* 'server_url' => 'http://ftp.drupal.org/files/translations/l10n_server.xml',
* 'update_url' => 'http://ftp.drupal.org/files/translations/%core/%project/%project-%release.%language.po',
* ),
* );
*/
function hook_l10n_servers() {
// This hook is used to specify the default localization server(s).
// Additionally server data can be specified on a per project basis in the
// .info file or using the hook_l10n_update_projects_alter().
module_load_include('inc', 'l10n_update');
$server = l10n_update_default_server();
return array($server['name'] => $server );
}
/**
* Alter the list of project to be updated by l10n update.
*
* l10n_update uses the same list of projects as update module. Using this hook
* the list can be altered.
*
* @param array $projects
* Array of projects.
*/
function hook_l10n_update_projects_alter(&$projects) {
// The $projects array contains the project data produced by
// update_get_projects(). A number of the array elements are described in
// the documentation of hook_update_projects_alter().
// In the .info file of a project a localization server can be specified.
// Using this hook the localization server specification can be altered or
// added. The 'l10n path' element is optional but can be specified to override
// the translation download path specified in the 10n_server.xml file.
$projects['existing_example_project'] = array(
'info' => array(
'l10n server' => 'example.com',
'l10n url' => 'http://example.com/files/translations/l10n_server.xml',
'l10n path' => 'http://example.com/files/translations/%core/%project/%project-%release.%language.po',
),
);
// With this hook it is also possible to add a new project wich does not
// exist as a real module or theme project but is treated by the localization
// update module as one. The below data is the minumum to be specified.
// As in the previous example the 'l10n path' element is optional.
$projects['new_example_project'] = array(
'project_type' => 'module',
'name' => 'new_example_project',
'info' => array(
'version' => '6.x-1.5',
'core' => '6.x',
'l10n server' => 'example.com',
'l10n url' => 'http://example.com/files/translations/l10n_server.xml',
'l10n path' => 'http://example.com/files/translations/%core/%project/%project-%release.%language.po',
),
);
}
<?php
/**
* @file
* Reusable API for creating and running l10n update batches.
*/
// module_load_include will not work in batch.
include_once 'l10n_update.check.inc';
/**
* Create a batch to just download files.
*
* @param $updates
* Translations sources to be downloaded.
* Note: All update sources must have a 'fileurl'.
* @return array
* A batch definition for this download.
*/
function l10n_update_batch_download($updates) {
foreach ($updates as $update) {
$id = $update->filename;
$operations[] = array('_l10n_update_batch_download', array($id, $update));
}
return _l10n_update_create_batch($operations);
}
/**
* Create a batch to just import files.
*
* All update sources must have a 'uri'.
*
* @param $updates
* Translations sources to be imported.
* Note: All update sources must have a 'fileurl'.
* @param $import_mode
* Import mode. How to treat existing and modified translations.
* @return array
* A batch definition for this import.
*/
function l10n_update_batch_import($updates, $import_mode) {
foreach ($updates as $update) {
$id = $update->filename;
$operations[] = array('_l10n_update_batch_import', array($id, $update, $import_mode));
}
return _l10n_update_create_batch($operations);
}
/**
* Create a big batch for multiple projects and languages.
*
* @param $updates
* Array of update sources to be run.
* @param $mode
* Import mode. How to treat existing and modified translations.
* @return array
*/
function l10n_update_batch_multiple($updates, $import_mode) {
foreach ($updates as $update) {
$id = $update->filename;
if ($update->type == 'download') {
$operations[] = array('_l10n_update_batch_download', array($id, $update));
$operations[] = array('_l10n_update_batch_import', array($id, NULL, $import_mode));
}
else {
$operations[] = array('_l10n_update_batch_import', array($id, $update, $import_mode));
}
// This one takes always parameters from results.
$operations[] = array('_l10n_update_batch_history', array($id));
}
if (!empty($operations)) {
return _l10n_update_create_batch($operations);
}
}
/**
* Create batch stub for this module.
*
* @param $operations
* Operations to perform in this batch.
* @return array
* A batch definition:
* - 'operations': Batch operations
* - 'title': Batch title.
* - 'init_message': Initial batch UI message.
* - 'error_message': Batch error message.
* - 'file': File containing callback function.
* - 'finished': Batch completed callback function.
*/
function _l10n_update_create_batch($operations = array()) {
$t = get_t();
$batch = array(
'operations' => $operations,
'title' => $t('Updating translation.'),
'init_message' => $t('Downloading and importing files.'),
'error_message' => $t('Error importing interface translations'),
'file' => drupal_get_path('module', 'l10n_update') . '/l10n_update.batch.inc',
'finished' => '_l10n_update_batch_finished',
);
return $batch;
}
/**
* Batch process: Download a file.
*
* @param $id
* Batch id to identify batch results.
* Result of this batch function are stored in $context['result']
* identified by this $id.
* @param $file
* File to be downloaded.
* @param $context
* Batch context array.
*/
function _l10n_update_batch_download($id, $file, &$context) {
$t = get_t();
if (l10n_update_source_download($file)) {
$context['message'] = $t('Importing: %name.', array('%name' => $file->filename));
$context['results'][$id] = array('file' => $file);
}
else {
$context['results'][$id] = array('file' => $file, 'fail' => TRUE);
}
}
/**
* Batch process: Update the download history table.
*
* @param $id
* Batch id to identify batch results.
* Result of this batch function are stored in $context['result']
* identified by this $id.
* @param $context
* Batch context array.
*/
function _l10n_update_batch_history($id, &$context) {
$t = get_t();
// The batch import is performed in a number of steps. History update is
// always the last step. The details of the downloaded/imported file are
// stored in $context['results'] array.
if (isset($context['results'][$id]['file']) && !isset($context['results'][$id]['fail'])) {
$file = $context['results'][$id]['file'];
l10n_update_source_history($file);
$context['message'] = $t('Imported: %name.', array('%name' => $file->filename));
}
}
/**
* Batch process: Import translation file.
*
* This takes a file parameter or continues from previous batch
* which should have downloaded a file.
*
* @param $id
* Batch id to identify batch results.
* Result of this batch function are stored in $context['result']
* identified by this $id.
* @param $file
* File to be imported. If empty, the file will be taken from $context['results'].
* @param $mode
* Import mode. How to treat existing and modified translations.
* @param $context
* Batch context array.
*/
function _l10n_update_batch_import($id, $file, $mode, &$context) {
$t = get_t();
// The batch import is performed in two or three steps.
// If import is performed after file download the file details of the download
// are used which are stored in the $context['results'] array.
// If a locally stored file is imported, the file details are placed in $file.
if (empty($file) && isset($context['results'][$id]['file']) && !isset($context['results'][$id]['fail'])) {
$file = $context['results'][$id]['file'];
}
if ($file) {
if ($import_result = l10n_update_source_import($file, $mode)) {
$context['message'] = $t('Imported: %name.', array('%name' => $file->filename));
$context['results'][$id] = array_merge((array)$context['results'][$id], $import_result, array('file' => $file));
}
else {
$context['results'][$id] = array_merge((array)$context['results'][$id], array('fail' => TRUE), array('file' => $file));
}
}
}
/**
* Batch finished callback: Set result message.
*
* @param $success
* TRUE if batch succesfully completed.
* @param $results
* Batch results.
*/
function _l10n_update_batch_finished($success, $results) {
$totals = array(); // Sum of added, updated and deleted translations.
$total_skip = 0; // Sum of skipped translations
$messages = array(); // User feedback messages.
$project_fail = $project_success = array(); // Project names of succesfull and failed imports.
$t = get_t();
if ($success) {
// Summarize results of added, updated, deleted and skiped translations.
// Added, updated and deleted are summarized per language to be displayed accordingly.
foreach ($results as $result) {
if (isset($result['fail'])) {
// Collect project names of the failed imports.
$project_fail[$result['file']->name] = $result['file']->name;
}
else {
$language = $result['language'];
// Initialize variables to prevent PHP Notices.
if (!isset($totals[$language])) {
$totals[$language] = array();
$totals[$language]['add'] = $totals[$language]['update'] = $totals[$language]['delete'] = 0;
}
// Summarize added, updated, deleted and skiped translations.
$totals[$language]['add'] += $result['add'];
$totals[$language]['update'] += $result['update'];
$totals[$language]['delete'] += $result['delete'];
$total_skip += $result['skip'];
// Collect project names of the succesfull imports.
$project_success[$result['file']->name] = $result['file']->name;
}
}
// Messages of succesfull translation update results.
if ($project_success) {
$messages[] = format_plural(count($project_success), 'One project updated: @projects.', '@count projects updated: @projects.', array('@projects' => implode(', ', $project_success)));
$languages = language_list();
foreach ($totals as $language => $total) {
$messages[] = $t('%language translation strings added: !add, updated: !update, deleted: !delete.', array(
'%language' => $languages[$language]->name,
'!add' => $total['add'],
'!update' => $total['update'],
'!delete' => $total['delete'],
));
}
drupal_set_message(implode("<br />\n", $messages));
// Warning for disallowed HTML.
if ($total_skip) {
drupal_set_message(
format_plural(
$total_skip,
'One translation string was skipped because it contains disallowed HTML. See !log_messages for details.',
'@count translation strings were skipped because they contain disallowed HTML. See !log_messages for details.',
array('!log_messages' => l(t('Recent log messages'), 'admin/reports/dblog'))),
'warning');
}
}
// Error for failed imports.
if ($project_fail) {
drupal_set_message(
format_plural(
count($project_fail),
'Translations of one project were not imported: @projects.',
'Translations of @count projects were not imported: @projects',
array('@projects' => implode(', ', $project_fail))),
'error');
}
}
else {
drupal_set_message($t('Error importing translations.'), 'error');
}
}
This diff is collapsed.
<?php
/**
* @file
* Drush interface to l10n-update functionalities.
*/
/**
* Implements hook_drush_command().
*/
function l10n_update_drush_command() {
$commands = array();
$commands['l10n-update-refresh'] = array(
'description' => 'Refresh available information.',
);
$commands['l10n-update-status'] = array(
'description' => 'Show translation status of available projects.',
'options' => array(
'languages' => 'Comma separated list of languages. Defaults to all available languages.',
)
);
$commands['l10n-update'] = array(
'description' => 'Update translations.',
'options' => array(
'languages' => 'Comma separated list of languages. Defaults to all available languages.',
'mode' => 'Allowed values: overwrite, keep. Default value: keep.'
),
);
return $commands;
}
/**
* Callback for command l10n-update-refresh.
*/
function drush_l10n_update_refresh() {
module_load_include('admin.inc', 'l10n_update');
// Fake $form_state to leverage _submit function.
$form_state = array(
'values' => array('op' => t('Refresh information'))
);
l10n_update_admin_import_form_submit(NULL, $form_state);
}
/**
* Validate command l10n-update-status.
*/
function drush_l10n_update_status_validate() {
_drush_l10n_update_validate_languages();
}
/**
* Callback for command l10n-update-status.
*/
function drush_l10n_update_status() {
$updates = _drush_l10n_update_get_updates();
if (!is_null($updates)) {
$languages = drush_get_option('languages');
// Table header.
$table = array();
$header = array(dt('Project'));
foreach ($languages as $lang => $language) {
$header[] = $language . ' status';
}
$table[] = $header;
// Iterate projects to obtain per language status.
$projects = l10n_update_get_projects();
$history = l10n_update_get_history();
foreach ($projects as $name => $project) {
$row = array();
// Project.
$title = isset($project->title) ? $project->title : $project->name;
$row[] = $title . ' ' . $project->version;
// Language status.
foreach ($languages as $lang => $language) {
$current = isset($history[$name][$lang]) ? $history[$name][$lang] : NULL;
$update = isset($updates[$name][$lang]) ? $updates[$name][$lang] : NULL;
if ($update) {
$row[] = ($update->type == 'download') ? t('Remote update available'):t('Local update available');
}
elseif ($current) {
$row[] = t('Up to date');
}
else {
$row[] = t('No information');
}
}
$table[] = $row;
}
drush_print_table($table, TRUE);
}
else {
drush_log(dt('No projects or languages to update.'), 'ok');
}
}
/**
* Validate command l10n-update.
*/
function drush_l10n_update_validate() {
_drush_l10n_update_validate_languages();
// Check provided update mode is valid.
$mode = drush_get_option('mode', 'keep');
if (!in_array($mode, array('keep', 'overwrite'))) {
return drush_set_error('L10N_UPDATE_INVALID_MODE', dt('Invalid update mode. Valid options are keep, overwrite.'));
}
}
/**
* Callback for command l10n-update.
*/
function drush_l10n_update() {
$updates = _drush_l10n_update_get_updates();
if (!is_null($updates)) {
if (count($updates) > 0) {
drush_log(dt('Found @count projects to update.', array('@count' => count($updates))), 'status');
// Batch update all projects for selected languages.
$mode = drush_get_option('mode', 'keep');
$languages = drush_get_option('languages');
module_load_include('batch.inc', 'l10n_update');
$updates = _l10n_update_prepare_updates($updates, NULL, array_keys($languages));
$batch = l10n_update_batch_multiple($updates, $mode);
drush_log($batch['title'], 'status');
drush_log($batch['init_message'], 'status');
batch_set($batch);
drush_backend_batch_process();
}
else {
drush_log(dt('All translations up to date'), 'status');
}
}
}
/**
* Helper function to validate languages.
*
* Used by _validate hooks.
* 1. Check other languages than english are available.
* 2. Check user provided languages are valid.
*/
function _drush_l10n_update_validate_languages() {
// Check there're installed other languages than english.
$installed_languages = l10n_update_language_list();
if (empty($installed_languages)) {
return drush_set_error('L10N_UPDATE_NO_LANGUAGES', dt('No languages to update.'));
}
// Check provided languages are valid.
$languages = drush_get_option('languages', '');
$languages = array_map('trim', _convert_csv_to_array($languages));
if (count($languages)) {
foreach ($languages as $key => $lang) {
if (!isset($installed_languages[$lang])) {
drush_set_error('L10N_UPDATE_INVALID_LANGUAGE', dt('Language @lang is not installed.', array('@lang' => $lang)));
}
else {
unset($languages[$key]);
$languages[$lang] = $installed_languages[$lang];
}
}
if (drush_get_error() != DRUSH_SUCCESS) {
drush_print(dt('Available languages: @languages', array('@languages' => implode(', ', array_keys($installed_languages)))));
}
}
else {
$languages = $installed_languages;
}
drush_set_option('languages', $languages);
}
/**
* Helper function to obtain $updates.
*
* @return $updates array or NULL.
*/
function _drush_l10n_update_get_updates() {
$projects = l10n_update_get_projects();
if ($projects) {
$history = l10n_update_get_history();
drush_log(dt('Fetching update information for all projects / all languages.'), 'status');
module_load_include('check.inc', 'l10n_update');
$available = l10n_update_available_releases();
$updates = l10n_update_build_updates($history, $available);
return $updates;
}
else {
drush_log(dt('No projects or languages to update.'), 'ok');
}
}
This diff is collapsed.
name = Localization update
description = Provides automatic downloads and updates for translations.
dependencies[] = locale
core = 7.x
package = Multilingual
files[] = l10n_update.admin.inc
files[] = l10n_update.api.php
files[] = l10n_update.batch.inc
files[] = l10n_update.check.inc
files[] = l10n_update.drush.inc
files[] = l10n_update.inc
files[] = l10n_update.install
files[] = l10n_update.locale.inc
files[] = l10n_update.module
files[] = l10n_update.parser.inc
files[] = l10n_update.project.inc
; Information added by drupal.org packaging script on 2012-02-06
version = "7.x-1.0-beta3"
core = "7.x"
project = "l10n_update"
datestamp = "1328563848"
<?php
/**
* @file
* Install file for l10n remote updates.
*/
/**
* Implements hook_schema().
*/
function l10n_update_schema() {
$schema['l10n_update_project'] = array(
'description' => 'Update information for project translations.',
'fields' => array(
'name' => array(
'description' => 'A unique short name to identify the project.',
'type' => 'varchar',
'length' => '50',
'not null' => TRUE,
),
'project_type' => array(
'description' => 'Project type, may be core, module, theme',
'type' => 'varchar',
'length' => '50',
'not null' => TRUE,
),
'core' => array(
'description' => 'Core compatibility string for this project.',
'type' => 'varchar',
'length' => '128',
'not null' => TRUE,
'default' => '',
),
'version' => array(
'description' => 'Human readable name for project used on the interface.',
'type' => 'varchar',
'length' => '128',
'not null' => TRUE,
'default' => '',
),
'l10n_server' => array(
'description' => 'Localization server for this project.',
'type' => 'varchar',
'length' => '255',
'not null' => TRUE,
'default' => '',
),
'l10n_path' => array(
'description' => 'Server path this project updates.',
'type' => 'varchar',
'length' => '255',
'not null' => TRUE,
'default' => '',
),
'status' => array(
'description' => 'Status flag. TBD',
'type' => 'int',
'not null' => TRUE,
'default' => 1,
),
),
'primary key' => array('name'),
);
$schema['l10n_update_file'] = array(
'description' => 'File and download information for project translations.',
'fields' => array(
'project' => array(
'description' => 'A unique short name to identify the project.',
'type' => 'varchar',
'length' => '50',
'not null' => TRUE,
),
'language' => array(
'description' => 'Reference to the {languages}.language for this translation.',
'type' => 'varchar',
'length' => '12',
'not null' => TRUE,
),
'type' => array(
'description' => 'File origin: download or localfile',
'type' => 'varchar',
'length' => '50',
'not null' => TRUE,
'default' => '',
),
'filename' => array(
'description' => 'Link to translation file for download.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'fileurl' => array(
'description' => 'Link to translation file for download.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'uri' => array(
'description' => 'File system path for importing the file.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'timestamp' => array(
'description' => 'Unix timestamp of the time the file was downloaded or saved to disk. Zero if not yet downloaded',
'type' => 'int',
'not null' => FALSE,
'disp-width' => '11',
'default' => 0,
),
'version' => array(
'description' => 'Version tag of the downloaded file.',
'type' => 'varchar',
'length' => '128',
'not null' => TRUE,
'default' => '',
),
'status' => array(
'description' => 'Status flag. TBD',
'type' => 'int',
'not null' => TRUE,
'default' => 1,
),
'last_checked' => array(
'description' => 'Unix timestamp of the last time this translation was downloaded from or checked at remote server and confirmed to be the most recent release available.',
'type' => 'int',
'not null' => FALSE,
'disp-width' => '11',
'default' => 0,
),
),
'primary key' => array('project', 'language'),
);
$schema['cache_l10n_update'] = drupal_get_schema_unprocessed('system', 'cache');
$schema['cache_l10n_update']['description'] = 'Cache table for the Localization Update module to store information about available releases, fetched from central server.';
return $schema;
}
/**
* Implements hook_schema_alter().
*/
function l10n_update_schema_alter(&$schema) {
$schema['locales_target']['fields']['l10n_status'] = array(
'type' => 'int',
'not null' => TRUE,
'default' => 0,
);
}
/**
* Implements hook_install().
*/
function l10n_update_install() {
db_add_field('locales_target', 'l10n_status', array('type' => 'int', 'not null' => TRUE, 'default' => 0));
variable_set('l10n_update_rebuild_projects', 1);
}
/**
* Implements hook_uninstall().
*/
function l10n_update_uninstall() {
db_drop_field('locales_target', 'l10n_status');
variable_del('l10n_update_check_disabled');
variable_del('l10n_update_check_frequency');
variable_del('l10n_update_check_mode');
variable_del('l10n_update_default_server');
variable_del('l10n_update_default_update_url');
variable_del('l10n_update_download_store');
variable_del('l10n_update_import_mode');
variable_del('l10n_update_rebuild_projects');
}
/**
* Implements hook_requirements().
*/
function l10n_update_requirements($phase) {
if ($phase == 'runtime') {
if (l10n_update_get_projects() && l10n_update_language_list()) {
$requirements['l10n_update']['title'] = t('Translation update status');
if (l10n_update_available_updates()) {
$requirements['l10n_update']['severity'] = REQUIREMENT_WARNING;
$requirements['l10n_update']['value'] = t('There are available updates');
$requirements['l10n_update']['description'] = t(
'There are new or updated translations available for currently installed modules and themes. To check for updates, you can visit the <a href="@check_manually">translation update page</a>.',
array(
'@check_manually' => url('admin/config/regional/translate/update')
)
);
}
else {
$requirements['l10n_update']['severity'] = REQUIREMENT_OK;
$requirements['l10n_update']['value'] = t('All your translations are up to date');
}
}
else {
$requirements['l10n_update']['title'] = t('Translation update status');
$requirements['l10n_update']['value'] = t('No update data available');
$requirements['l10n_update']['severity'] = REQUIREMENT_WARNING;
//$requirements['update_core']['reason'] = UPDATE_UNKNOWN;
$requirements['l10n_update']['description'] = _l10n_update_no_data();
}
return $requirements;
}
// We must always return array, the installer doesn't use module_invoke_all()
return array();
}
/**
* Add status field to locales_target.
*/
function l10n_update_update_6001() {
if (!db_field_exists('locales_target', 'l10n_status')) {
db_add_field('locales_target', 'l10n_status', array('type' => 'int', 'not null' => TRUE, 'default' => 0));
}
return t('Added l10n_status field to locales_target.');
}
/**
* Change status field name to l10n_status.
*/
function l10n_update_update_6002() {
// I18n Strings module adds a 'status' column to 'locales_target' table.
// L10n Update module previously added a column with the same name. To avoid
// any collision we change the column name here, but only if it was added by
// L10n Update module.
if (!db_field_exists('locales_target', 'l10n_status') && db_field_exists('locales_target', 'status') && !db_table_exists('i18n_strings')) {
db_change_field('locales_target', 'status', 'l10n_status', array('type' => 'int', 'not null' => TRUE, 'default' => 0));
}
// Just in case someone did install I18n Strings, we still need to make sure
// the 'l10n_status' column gets created.
elseif (!db_field_exists('locales_target', 'l10n_status')) {
db_add_field('locales_target', 'l10n_status', array('type' => 'int', 'not null' => TRUE, 'default' => 0));
}
return t('Resolved possible l10n_status field conflict in locales_target.');
}
/**
* Rename filepath to uri in {l10n_update_file} table.
*/
function l10n_update_update_7001() {
// Only do this update if the field exists from D6.
// If it doesn't, we've got a pure D7 site that doesn't need it.
if (db_field_exists('l10n_update_file', 'filepath')) {
db_change_field('l10n_update_file', 'filepath', 'uri', array(
'description' => 'File system path for importing the file.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
));
}
}
/**
* Delete 'last_updated' field from {l10n_update_file} table.
*/
function l10n_update_update_7002() {
db_drop_field('l10n_update_file', 'last_updated');
}
/**
* Delete 'import_date' field from {l10n_update_file} table.
*/
function l10n_update_update_7003() {
db_drop_field('l10n_update_file', 'import_date');
}
/**
* Create {cache_l10n_update} table.
*/
function l10n_update_update_7004() {
if (!db_table_exists('cache_l10n_update')) {
$schema = drupal_get_schema_unprocessed('system', 'cache');
$schema['description'] = 'Cache table for the Localization Update module to store information about available releases, fetched from central server.';
db_create_table('cache_l10n_update', $schema);
}
}
This diff is collapsed.
This diff is collapsed.
<?php
/**
* @file
* Extends the update parser to work with releases
*
* The update parser uses version tag to index releases. We will use 'language' and 'tag'
*
* @todo Parse languages too
*
* @todo Update the server side and get rid of this
*/
module_load_include('inc', 'l10n_update');
/**
* Get server information
*/
function l10n_update_get_server($server) {
// Fetch up to date information if available
if (!empty($server['server_url']) && ($fetch = l10n_update_fetch_server($server['server_url']))) {
$server = array_merge($server, $fetch);
}
// If we have an update url this is ok, otherwise we return none
if (!empty($server['update_url'])) {
return $server;
}
else {
return FALSE;
}
}
/**
* Fetch remote server metadata from a server URL
*
* @param unknown_type $server_url
* @return unknown_type
*/
function l10n_update_fetch_server($url) {
$xml = l10n_update_http_request($url);
if (isset($xml->data)) {
$data[] = $xml->data;
$parser = new l10n_update_xml_parser;
return $parser->parse($xml->data);
}
else {
return FALSE;
}
}
/**
* Parser for server metadata
*/
class l10n_update_xml_parser {
var $current_language;
var $current_server;
var $current_languages;
var $servers;
/**
* Parse an XML data file.
*
* It can contain information for one or more l10n_servers
*
* Example data, http://ftp.drupal.org/files/translations/l10n_server.xml
*/
function parse($data) {
$parser = xml_parser_create();
xml_set_object($parser, $this);
xml_set_element_handler($parser, 'start', 'end');
xml_set_character_data_handler($parser, "data");
xml_parse($parser, $data);
xml_parser_free($parser);
//return $this->servers;
return $this->current_server;
}
function start($parser, $name, $attr) {
$this->current_tag = $name;
switch ($name) {
case 'L10N_SERVER':
unset($this->current_object);
$this->current_server = array();
$this->current_object = &$this->current_server;
break;
case 'LANGUAGES':
unset($this->current_object);
$this->current_languages = array();
$this->current_object = &$this->current_languages;
//$this->current_object = &$this->current_release;
break;
case 'LANGUAGE':
unset($this->current_object);
$this->current_language = array();
$this->current_object = &$this->current_language;
break;
}
}
function end($parser, $name) {
switch ($name) {
case 'L10N_SERVER':
unset($this->current_object);
$this->servers[$this->current_server['name']] = $this->current_server;
//$this->current_server = array();
break;
case 'LANGUAGE':
unset($this->current_object);
$this->current_languages[$this->current_language['code']] = $this->current_language;
$this->current_language = array();
break;
case 'LANGUAGES':
$this->current_server['languages'] = $this->current_languages;
break;
default:
$this->current_object[strtolower($this->current_tag)] = trim($this->current_object[strtolower($this->current_tag)]);
$this->current_tag = '';
}
}
function data($parser, $data) {
if ($this->current_tag && !in_array($this->current_tag, array('L10N_SERVER', 'LANGUAGES', 'LANGUAGE'))) {
$tag = strtolower($this->current_tag);
if (isset($this->current_object[$tag])) {
$this->current_object[$tag] .= $data;
}
else {
$this->current_object[$tag] = $data;
}
}
}
}
<?php
/**
* @file
* Library for querying Drupal projects
*
* Most code is taken from update module. We don't want to depend on it though as it may not be enabled.
*
* For each project, the information about where to fetch translations may be specified in the info files
* as follows:
*
* - Localization server to be used for this project. Defaults to http://localize.drupal.org
* l10n server = localize.drupal.org
* (This should be enough if the server url, the one below, is defined somewhere else)
*
* - Metadata information for the localization server
* l10n url = http://ftp.drupal.org/files/translations/l10n_server.xml
* (We can fetch *all* the information we need from this single url)
*
* - Translation file URL template, will be used to build the file url to download
* l10n path = http://ftp.drupal.org/files/translations/%core/%project/%project-%release.%language.po
* (Alternatively you can use the %filename variable that will default to '%project-%release.%language.po')
*/
/**
* Rebuild project list
*
* @return array
*/
function l10n_update_build_projects() {
module_load_include('inc', 'l10n_update');
// Get all stored projects, including disabled ones
$current = l10n_update_get_projects(NULL, TRUE);
// Now get the new project list, just enabled ones
$projects = l10n_update_project_list();
// Mark all previous projects as disabled and store new project data
db_update('l10n_update_project')
->fields(array(
'status' => 0,
))
->execute();
$default_server = l10n_update_default_server();
if (module_exists('update')) {
$projects_info = update_get_available(TRUE);
}
foreach ($projects as $name => $data) {
if (isset($projects_info[$name]['releases']) && $projects_info[$name]['project_status'] != 'not-fetched') {
// Find out if a dev version is installed.
if (preg_match("/^[0-9]+\.x-([0-9]+)\..*-dev$/", $data['info']['version'], $matches)) {
// Find a suitable release to use as alternative translation.
foreach ($projects_info[$name]['releases'] as $project_release) {
// The first release with the same major release number which is not
// a dev release is the one. Releases are sorted the most recent first.
if ($project_release['version_major'] == $matches[1] &&
(!isset($project_release['version_extra']) || $project_release['version_extra'] != 'dev')) {
$release = $project_release;
break;
}
}
}
elseif ($name == "drupal" || preg_match("/HEAD/", $data['info']['version'], $matches)) {
// Pick latest available release.
$release = array_shift($projects_info[$name]['releases']);
}
if (!empty($release['version'])) {
$data['info']['version'] = $release['version'];
}
unset($release);
}
$data += array(
'version' => isset($data['info']['version']) ? $data['info']['version'] : '',
'core' => isset($data['info']['core']) ? $data['info']['core'] : DRUPAL_CORE_COMPATIBILITY,
// The project can have its own l10n server, we use default if not
'l10n_server' => isset($data['info']['l10n server']) ? $data['info']['l10n server'] : NULL,
// A project can provide the server url to fetch metadata, or the update url (path)
'l10n_url' => isset($data['info']['l10n url']) ? $data['info']['l10n url'] : NULL,
'l10n_path' => isset($data['info']['l10n path']) ? $data['info']['l10n path'] : NULL,
'status' => 1,
);
$project = (object) $data;
// Unless the project provides a full l10n path (update url), we try to build one
if (!isset($project->l10n_path)) {
$server = NULL;
if ($project->l10n_server || $project->l10n_url) {
$server = l10n_update_server($project->l10n_server, $project->l10n_url);
}
else {
// Use the default server
$server = l10n_update_server($default_server['name'], $default_server['server_url']);
}
if ($server) {
// Build the update path for this project, with project name and release replaced
$project->l10n_path = l10n_update_build_string($project, $server['update_url']);
}
}
// Create / update project record
$update = empty($current[$name]) ? array() : array('name');
drupal_write_record('l10n_update_project', $project, $update);
$projects[$name] = $project;
}
return $projects;
}
/**
* Get update module's project list
*
* @return array
*/
function l10n_update_project_list() {
$projects = array();
$disabled = variable_get('l10n_update_check_disabled', 0);
// Unlike update module, this one has no cache
_l10n_update_project_info_list($projects, system_rebuild_module_data(), 'module', $disabled);
_l10n_update_project_info_list($projects, system_rebuild_theme_data(), 'theme', $disabled);
// Allow other modules to alter projects before fetching and comparing.
drupal_alter('l10n_update_projects', $projects);
return $projects;
}
/**
* Refresh projects after enabling modules
*
* When new projects are installed, set a batch for locale import / update
*
* @param $modules
* Array of module names.
*/
function l10n_update_project_refresh($modules) {
module_load_include('check.inc', 'l10n_update');
$projects = array();
// Get all current projects, including the recently installed.
$current_projects = l10n_update_build_projects();
// Collect project data of newly installed projects.
foreach ($modules as $name) {
if (isset($current_projects[$name])) {
$projects[$name] = $current_projects[$name];
}
}
// If a translation is available and if update is required, lets go.
if ($projects && $available = l10n_update_check_projects($projects)) {
$history = l10n_update_get_history();
if ($updates = l10n_update_build_updates($history, $available)) {
module_load_include('batch.inc', 'l10n_update');
// Filter out updates in other languages. If no languages, all of them will be updated
$updates = _l10n_update_prepare_updates($updates);
$batch = l10n_update_batch_multiple($updates, variable_get('l10n_update_import_mode', LOCALE_IMPORT_KEEP));
batch_set($batch);
}
}
}
/**
* Populate an array of project data.
*
* Based on _update_process_info_list()
*
* @param $projects
* @param $list
* @param $project_type
* @param $disabled
* TRUE to include disabled projects too
*/
function _l10n_update_project_info_list(&$projects, $list, $project_type, $disabled = FALSE) {
foreach ($list as $file) {
if (!$disabled && empty($file->status)) {
// Skip disabled modules or themes.
continue;
}
// Skip if the .info file is broken.
if (empty($file->info)) {
continue;
}
// If the .info doesn't define the 'project', try to figure it out.
if (!isset($file->info['project'])) {
$file->info['project'] = l10n_update_get_project_name($file);
}
// If we still don't know the 'project', give up.
if (empty($file->info['project'])) {
continue;
}
// If we don't already know it, grab the change time on the .info file
// itself. Note: we need to use the ctime, not the mtime (modification
// time) since many (all?) tar implementations will go out of their way to
// set the mtime on the files it creates to the timestamps recorded in the
// tarball. We want to see the last time the file was changed on disk,
// which is left alone by tar and correctly set to the time the .info file
// was unpacked.
if (!isset($file->info['_info_file_ctime'])) {
$info_filename = dirname($file->uri) . '/' . $file->name . '.info';
$file->info['_info_file_ctime'] = filectime($info_filename);
}
$project_name = $file->info['project'];
if (!isset($projects[$project_name])) {
// Only process this if we haven't done this project, since a single
// project can have multiple modules or themes.
$projects[$project_name] = array(
'name' => $project_name,
'info' => $file->info,
'datestamp' => isset($file->info['datestamp']) ? $file->info['datestamp'] : 0,
'includes' => array($file->name => $file->info['name']),
'project_type' => $project_name == 'drupal' ? 'core' : $project_type,
);
}
else {
$projects[$project_name]['includes'][$file->name] = $file->info['name'];
$projects[$project_name]['info']['_info_file_ctime'] = max($projects[$project_name]['info']['_info_file_ctime'], $file->info['_info_file_ctime']);
}
}
}
/**
* Given a $file object (as returned by system_rebuild_module_data()), figure
* out what project it belongs to.
*
* Based on update_get_project_name().
*
* @param $file
* @return string
* @see system_get_files_database()
*/
function l10n_update_get_project_name($file) {
$project_name = '';
if (isset($file->info['project'])) {
$project_name = $file->info['project'];
}
elseif (isset($file->info['package']) && (strpos($file->info['package'], 'Core') === 0)) {
$project_name = 'drupal';
}
return $project_name;
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment