TimeTrex Community Edition v16.2.0
This commit is contained in:
214
tools/i18n/README.i18n
Normal file
214
tools/i18n/README.i18n
Normal file
@ -0,0 +1,214 @@
|
||||
TimeTrex Message Internationalization(i18n) README
|
||||
|
||||
by Open Source Consulting, S.A.
|
||||
|
||||
|
||||
= OVERVIEW =
|
||||
|
||||
TimeTrex contains english strings embedded throughout the PHP code and
|
||||
smarty templates. The GNU Gettext suite of tools has been chosen to aid
|
||||
with the tasks of:
|
||||
|
||||
- marking and extracting english strings from the source files
|
||||
- translating english strings into other languages
|
||||
- displaying the foreign language translations of the strings
|
||||
- merging in new english strings from the source files
|
||||
- merging in new translations from the translators
|
||||
|
||||
|
||||
It is a very good idea for anyone working in this area to read up more
|
||||
about gettext here:
|
||||
|
||||
http://www.gnu.org/software/gettext/
|
||||
|
||||
Below, we will explain more about how gettext is integrated with TimeTrex,
|
||||
how to properly code strings for translation, and how the translation
|
||||
maintenance tools work.
|
||||
|
||||
|
||||
= TTi18n Class =
|
||||
|
||||
TimeTrex now includes a class for handling all i18n related matters.
|
||||
Methods related to message translation include:
|
||||
|
||||
- chooseBestLocale() -- determines which locale to use for messages.
|
||||
|
||||
- gettext() -- returns a translated version of given input string.
|
||||
|
||||
|
||||
This class works basically like the standard setlocale(),
|
||||
bindtextdomain(), gettext() combination but it allows us to abstract the
|
||||
implementation a bit. For example, we may use PEAR's Translation2 class
|
||||
to parse the gettext files, instead of using PHP's native gettext()
|
||||
function.
|
||||
|
||||
For this reason, it is important that all translation calls in the system
|
||||
go through TTi18n::gettext() instead of calling gettext() directly.
|
||||
|
||||
|
||||
= Smarty Template Files =
|
||||
|
||||
We are using a slightly modified version of the gettext plugin for smarty.
|
||||
This plugin allows us to mark strings for translation with the following
|
||||
syntax:
|
||||
|
||||
{t}Here is some text that needs to be translated.{/t}
|
||||
|
||||
The only modification to this plugin is that we call TTi18n::gettext()
|
||||
(when defined) instead of gettext().
|
||||
|
||||
|
||||
= PHP Source Code Files =
|
||||
|
||||
Any strings within the source code that...
|
||||
|
||||
a) contain words and
|
||||
b) are intended for an end-user to see
|
||||
|
||||
...must be embedded within a call to TTi18n::gettext( "my string" ).
|
||||
|
||||
|
||||
There are two reasons for this:
|
||||
|
||||
1) gettext() must be called in order to perform the translation at
|
||||
runtime. This should be obvious.
|
||||
|
||||
2) The string extractor looks for "gettext( ... )" to know which strings
|
||||
to extract. Therefore, the following code is to be avoided:
|
||||
|
||||
$myvar = 'Please enter a value below';
|
||||
$myvar = TTi18n::gettext( $myvar );
|
||||
|
||||
While the above would do the right thing at runtime, the extractor
|
||||
would not know that this string represents english, so the above
|
||||
string would probably never get translated. Instead, this should be
|
||||
coded as:
|
||||
|
||||
$myvar = TTi18n::gettext('Please enter a value below');
|
||||
|
||||
Be careful to ensure that gettext() is only called once on a given string.
|
||||
Calling multiple times will probably not cause display problems, but each
|
||||
call adds some performance overhead.
|
||||
|
||||
|
||||
= The locale directories and messages.po files =
|
||||
|
||||
The locale directory exists at
|
||||
|
||||
timetrex/interface/locale
|
||||
|
||||
This directory contains:
|
||||
- the messages.pot master string dictionary with all english strings.
|
||||
- a sub-directory for each locale, eg es_ES for spanish/Spain.
|
||||
- the locale_stats.txt file which contains statistics about translations.
|
||||
|
||||
Each locale sub-directory contains another sub-directory LC_MESSAGES which
|
||||
in turn contains messages.po and (possibly) messages.mo. messages.po will
|
||||
contain the translations for that particular locale. messages.mo is a
|
||||
compiled form of the .po file that is necessary for GNU gettext to
|
||||
function -- however, it is not always required when using the Translation2
|
||||
library.
|
||||
|
||||
*** IMPORTANT ***
|
||||
|
||||
In the interest of avoiding possible encoding headaches, we are
|
||||
standardizing on the use UTF-8 encoded messages.po files. That is, all
|
||||
messages.po should be saved as UTF-8 before they are placed/updated into
|
||||
the tree.
|
||||
|
||||
Why? It has been observed that the Translation2 library does not properly
|
||||
convert encodings. While it may be possible to work around this, UTF-8 makes
|
||||
things simple all around.
|
||||
|
||||
|
||||
= The Translation helper tools =
|
||||
|
||||
These files live in tools/i18n. ( Though they could be anywhere. )
|
||||
|
||||
Each of these should be run from the directory in which it is saved.
|
||||
|
||||
mklocales.php
|
||||
This script creates and initializes the locale directories beneath the
|
||||
interface/locale directory. Edit the script and re-run to add a new
|
||||
locale directory and associated messages.po file.
|
||||
|
||||
gen_master_pot.sh
|
||||
This file generates a master messages.pot file in the interface/locale
|
||||
directory. This file is then used as the source when merging to
|
||||
individual locales.
|
||||
|
||||
mergelocale.sh
|
||||
This script merges new strings from messages.pot master file into a
|
||||
messages.po file for a single locale. It also generates the binary
|
||||
messages.mo file. Typically, this script is only called by mergelocales.sh
|
||||
|
||||
mergelocales.php
|
||||
This script merges new strings from message.pot master file into the
|
||||
messages.po files for all existings locale directories, creates the
|
||||
binary messages.mo files, and updates the translation stats.
|
||||
|
||||
calc_l10n_stats.php
|
||||
This script calculates the percentage of the application that has been
|
||||
translated for each available locale. It writes out a csv file that can
|
||||
be read by a PHP script to display the stats on a web page.
|
||||
|
||||
notify_translator.php
|
||||
This file notifies the translator by e-mail when new source
|
||||
strings are available for TimeTrex. Be careful not to run this by
|
||||
accident, as you may end up spamming people unnecessarily.
|
||||
|
||||
tsmarty2c.php
|
||||
This script is part of the smarty gettext plugin, and is used to extract
|
||||
the strings that can be translated from the smarty templates. This
|
||||
script is called by the gen_master_pot.sh script.
|
||||
|
||||
updatetranslation.sh
|
||||
This script is used to merge a single messages.po file
|
||||
provided by a translator with the corresponding messages.po files within
|
||||
TimeTrex.
|
||||
|
||||
|
||||
= Using the translation helper tools on a daily basis =
|
||||
|
||||
These are the typical events that happen during application development:
|
||||
|
||||
1) New strings get added into the PHP source or smarty templates.
|
||||
|
||||
2) A translator sent in new translations.
|
||||
|
||||
3) We need to add a new locale (language) for translation.
|
||||
|
||||
|
||||
Let's look at them individually:
|
||||
|
||||
1) New strings get added into the PHP source or smarty templates.
|
||||
|
||||
In this case, we need to a) update the master .pot file, b) merge
|
||||
those changes into the indvidual messages.po files, c) generate the binary
|
||||
messages.mo files and d) update the locale stats. It is as simple as:
|
||||
|
||||
$ ./gen_master_pot.sh
|
||||
$ ./mergelocales.php
|
||||
|
||||
|
||||
2) A translator sent in new translations.
|
||||
|
||||
Here, we just need to merge the translator's changes into a single
|
||||
messages.po file for a single locale. Then we want to update the binary
|
||||
message.mo file and the locale stats. Let's suppose we have just received
|
||||
spanish translations ( es_ES ) and have saved them in /tmp/messages.po.
|
||||
|
||||
$ ./update_translation.sh es_ES /tmp/messages.po
|
||||
$ ./calc_l10n_stats.php
|
||||
|
||||
|
||||
3) We need to add a new locale (language) for translation.
|
||||
|
||||
Let's suppose we have found an Italian translator, and now we want to add
|
||||
an it_IT locale. All we do is:
|
||||
|
||||
$ vi mklocales.php # Now uncomment the line with // 'it_IT'.
|
||||
$ ./mklocales.php
|
||||
|
||||
We could also make all the known locales by uncommenting all the locale
|
||||
lines in this script, and then re-running it.
|
157
tools/i18n/calc_l10n_stats.php
Normal file
157
tools/i18n/calc_l10n_stats.php
Normal file
@ -0,0 +1,157 @@
|
||||
#!/usr/bin/php
|
||||
<?php
|
||||
/*********************************************************************************
|
||||
*
|
||||
* TimeTrex is a Workforce Management program developed by
|
||||
* TimeTrex Software Inc. Copyright (C) 2003 - 2021 TimeTrex Software Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License version 3 as published by
|
||||
* the Free Software Foundation with the addition of the following permission
|
||||
* added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
|
||||
* WORK IN WHICH THE COPYRIGHT IS OWNED BY TIMETREX, TIMETREX DISCLAIMS THE
|
||||
* WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program; if not, see http://www.gnu.org/licenses or write to the Free
|
||||
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA.
|
||||
*
|
||||
*
|
||||
* You can contact TimeTrex headquarters at Unit 22 - 2475 Dobbin Rd. Suite
|
||||
* #292 West Kelowna, BC V4T 2E9, Canada or at email address info@timetrex.com.
|
||||
*
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License
|
||||
* version 3, these Appropriate Legal Notices must retain the display of the
|
||||
* "Powered by TimeTrex" logo. If the display of the logo is not reasonably
|
||||
* feasible for technical reasons, the Appropriate Legal Notices must display
|
||||
* the words "Powered by TimeTrex".
|
||||
*
|
||||
********************************************************************************/
|
||||
/*
|
||||
* File Contributed By: Open Source Consulting, S.A. San Jose, Costa Rica.
|
||||
* http://osc.co.cr
|
||||
*/
|
||||
if ( PHP_SAPI != 'cli' ) {
|
||||
echo "This script can only be called from the Command Line.\n";
|
||||
exit;
|
||||
}
|
||||
|
||||
// Calculates percent complete statistics for all locales.
|
||||
// Must be run from tools/i18n directory.
|
||||
$root_dir = '../../interface/locale';
|
||||
if ( count( $argv ) > 1 ) {
|
||||
$root_dir = $argv[1];
|
||||
}
|
||||
|
||||
$d = opendir( $root_dir );
|
||||
|
||||
if ( $d ) {
|
||||
|
||||
echo "calculating locale statistics...\n";
|
||||
|
||||
$outpath = $root_dir . '/' . 'locale_stats.txt';
|
||||
$fh = fopen( $outpath, 'w' );
|
||||
|
||||
$ignore_dirs = [ '.', '..', 'CVS' ];
|
||||
while ( false !== ( $file = readdir( $d ) ) ) {
|
||||
if ( is_dir( $root_dir . '/' . $file ) && !in_array( $file, $ignore_dirs ) ) {
|
||||
$stats = calcStats( $root_dir, $file );
|
||||
$pct = $stats['pct_complete'];
|
||||
$team = $stats['team'];
|
||||
fwrite( $fh, "$file|$pct|$team\n" );
|
||||
}
|
||||
}
|
||||
closedir( $d );
|
||||
|
||||
fclose( $fh );
|
||||
|
||||
echo "done. stats saved in $outpath\n";
|
||||
}
|
||||
|
||||
function calcStats( $root_dir, $locale ) {
|
||||
$messages = 0;
|
||||
$translations = 0;
|
||||
$fuzzy = 0;
|
||||
|
||||
$team = '';
|
||||
|
||||
$path = $root_dir . '/' . $locale . '/LC_MESSAGES/messages.po';
|
||||
// echo "<li><b>$path</b>";
|
||||
if ( file_exists( $path ) ) {
|
||||
$lines = file( $path );
|
||||
|
||||
$in_msgid = false;
|
||||
$in_msgstr = false;
|
||||
$found_translation = false;
|
||||
$found_msg = false;
|
||||
foreach ( $lines as $line ) {
|
||||
// ignore comment lines
|
||||
if ( $line[0] == '#' ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse out the contributors.
|
||||
if ( strstr( $line, '"Language-Team: ' ) ) {
|
||||
$endpos = strpos( $line, '\n' );
|
||||
if ( $endpos === false ) {
|
||||
$endpos = strlen( $line ) - 2;
|
||||
}
|
||||
$len = strlen( '"Language-Team: ' );
|
||||
$field = substr( $line, $len, $endpos - $len );
|
||||
$names = explode( ',', $field );
|
||||
foreach ( $names as $name ) {
|
||||
if ( $name != 'none' ) {
|
||||
if ( $team != '' ) {
|
||||
$team .= ',';
|
||||
}
|
||||
$team .= trim( $name );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( strstr( $line, 'msgid "' ) ) {
|
||||
$in_msgid = true;
|
||||
$in_msgstr = false;
|
||||
$found_msg = false;
|
||||
$found_translation = false;
|
||||
}
|
||||
if ( $in_msgid && !$found_msg && strstr( $line, '"' ) && !strstr( $line, '""' ) ) {
|
||||
// echo "<li>msgid: $line";
|
||||
$found_msg = true;
|
||||
$messages++;
|
||||
} else if ( strstr( $line, 'msgstr "' ) ) {
|
||||
$in_msgstr = true;
|
||||
$in_msgid = false;
|
||||
}
|
||||
if ( $in_msgstr && $found_msg && !$found_translation ) {
|
||||
if ( strstr( $line, '"' ) && !strstr( $line, '""' ) ) {
|
||||
// echo "<li>msgstr: $line";
|
||||
$translations++;
|
||||
$found_translation = true;
|
||||
}
|
||||
} else if ( strstr( $line, '#, fuzzy' ) ) {
|
||||
$fuzzy++;
|
||||
}
|
||||
}
|
||||
}
|
||||
$translations -= $fuzzy;
|
||||
$pct_complete = $messages ? (int)( ( $translations / $messages ) * 100 ) : 0;
|
||||
|
||||
return [ 'pct_complete' => $pct_complete, 'team' => $team ];
|
||||
}
|
||||
|
||||
|
||||
?>
|
51
tools/i18n/gen_master_pot.sh
Executable file
51
tools/i18n/gen_master_pot.sh
Executable file
@ -0,0 +1,51 @@
|
||||
#!/bin/bash -x
|
||||
|
||||
##$License$##
|
||||
##
|
||||
# File Contributed By: Open Source Consulting, S.A. San Jose, Costa Rica.
|
||||
# http://osc.co.cr
|
||||
##
|
||||
|
||||
# This script generates the master .pot file containing english strings
|
||||
# for translation.
|
||||
#
|
||||
# These strings are parsed from:
|
||||
# * smarty templates between {t}some text{/t} blocks
|
||||
# * PHP Files containing TTi18n::gettext("some text") function calls.
|
||||
|
||||
# We could also parse from static DB text, but so far see no need for
|
||||
# doing so.
|
||||
|
||||
DEPTH=../..
|
||||
LOCALE_ROOT=$DEPTH/interface/locale
|
||||
POT_FILENAME=messages.pot
|
||||
|
||||
#---- Ensure pot file exists ----
|
||||
touch $LOCALE_ROOT/$POT_FILENAME
|
||||
|
||||
#---- Extract strings from templates ----
|
||||
#echo "Parsing templates..."
|
||||
#TMP_FILE=/tmp/gen_master_pot_tmp.txt
|
||||
#find $DEPTH/templates -name "*.tpl" | grep -v "\.git" | xargs -i php tsmarty2c.php \{\} | xgettext --from-code=UTF-8 --no-wrap --language=C --no-location -s --output-dir=$LOCALE_ROOT -o $POT_FILENAME -
|
||||
|
||||
#---- Extract strings from PHP Files ----
|
||||
# Note that we want to extract from TTi18n::gettext() calls.
|
||||
# xgettext ignores the "TTi18n::" bit and sees the gettext(). So it works.
|
||||
# When we removed the SMARTY parsing, also remove --join-existing option as this is now the first parse command that is run.
|
||||
echo "Parsing PHP Files..."
|
||||
find $DEPTH/ -name "*.php" | egrep -v "\.git|/templates_c|/vendor" > /tmp/xgettext_php.files
|
||||
xgettext --from-code=UTF-8 --no-wrap --keyword=getText --language=PHP -s -f /tmp/xgettext_php.files --output-dir=$LOCALE_ROOT -o $POT_FILENAME
|
||||
rm -f /tmp/xgettext_php.files
|
||||
|
||||
echo "Parsing JS Files..."
|
||||
#find $DEPTH/ -name "*.js" | grep -v "\.git" | xargs cat | sed 's/<br>/ /g' | sed 's/\n/ /g' | xgettext --from-code=UTF-8 --no-wrap --keyword=_ --join-existing --language=Javascript --no-location -s --output-dir=$LOCALE_ROOT -o $POT_FILENAME -
|
||||
find $DEPTH/ -name "*.js" | egrep -v "\.git|/node_modules|/tools/compile|/interface/html5/framework|/interface/html5/dist" > /tmp/xgettext_js.files
|
||||
xgettext --from-code=UTF-8 --no-wrap --keyword=_ --join-existing --language=Javascript -s -f /tmp/xgettext_js.files --output-dir=$LOCALE_ROOT -o $POT_FILENAME
|
||||
rm -f /tmp/xgettext_js.files
|
||||
|
||||
#---- Extract strings from DB Tables with static strings ----
|
||||
### Not necessary for TimeTrex at this time ###
|
||||
|
||||
|
||||
#---- Done ----
|
||||
echo "Done! POT File is in " $LOCALE_ROOT/$POT_FILENAME
|
55
tools/i18n/mergelocale.sh
Executable file
55
tools/i18n/mergelocale.sh
Executable file
@ -0,0 +1,55 @@
|
||||
#!/bin/bash
|
||||
|
||||
##$License$##
|
||||
##
|
||||
# File Contributed By: Open Source Consulting, S.A. San Jose, Costa Rica.
|
||||
# http://osc.co.cr
|
||||
##
|
||||
|
||||
# This script is useful for merging latest changes from the master .pot file into
|
||||
# a given locale file, and then generating the corresponding .mo binary file.
|
||||
|
||||
# This script is intended to be run from the tools/i18n directory
|
||||
|
||||
# arg1 should be the locale ID
|
||||
lc=$1
|
||||
cd ../../interface/locale
|
||||
|
||||
#Clear the en_US translation to avoid fuzzy translations, always create it from scratch.
|
||||
if [ $lc == 'en_US' ] ; then
|
||||
rm -f $lc/LC_MESSAGES/messages.po
|
||||
cat << EOF > $lc/LC_MESSAGES/messages.po
|
||||
# English translation for timetrex
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: timetrex\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: N/A\n"
|
||||
"Last-Translator: <N/A>\n"
|
||||
"Language-Team: <N/A>\n"
|
||||
"Language: en\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2015-07-10 17:45+0000\n"
|
||||
"X-Generator: Launchpad (build 17620)\n"
|
||||
EOF
|
||||
|
||||
fi
|
||||
|
||||
if [ $lc == 'yi_US' ] ; then
|
||||
#This is a test locale, change all strings to something that stands out so we can find untranslated ones easily.
|
||||
cat $lc/LC_MESSAGES/messages.po | sed -e '15,$s/msgstr ""/msgstr "Z"/g' > $lc/LC_MESSAGES/messages.po.tmp
|
||||
mv $lc/LC_MESSAGES/messages.po.tmp $lc/LC_MESSAGES/messages.po
|
||||
fi
|
||||
|
||||
#Don't use fuzzy matching with msgmerge to avoid issues with non-translating strings or mixing up of strings.
|
||||
touch $lc/LC_MESSAGES/messages.po && \
|
||||
msguniq -u --no-wrap --use-first $lc/LC_MESSAGES/messages.po -o $lc/LC_MESSAGES/messages.po && \
|
||||
msgmerge -N --no-wrap -s --update $lc/LC_MESSAGES/messages.po ./messages.pot && \
|
||||
msgfmt -c -o $lc/LC_MESSAGES/messages.mo $lc/LC_MESSAGES/messages.po
|
||||
|
||||
#Convert to .JSON file
|
||||
php ../../tools/i18n/po2json.php -i $lc/LC_MESSAGES/messages.po -o $lc/LC_MESSAGES/messages.json -n i18n_dictionary
|
||||
|
||||
rm -f $lc/LC_MESSAGES/messages.po~
|
37
tools/i18n/mergelocales.sh
Executable file
37
tools/i18n/mergelocales.sh
Executable file
@ -0,0 +1,37 @@
|
||||
#!/usr/bin/php
|
||||
<?php
|
||||
/*$License$*/
|
||||
/*
|
||||
* File Contributed By: Open Source Consulting, S.A. San Jose, Costa Rica.
|
||||
* http://osc.co.cr
|
||||
*/
|
||||
|
||||
// Merges new strings from messages.pot into messages.po for all
|
||||
// locales. Also compiles message.mo file, and calculates stats.
|
||||
|
||||
$cwd = getcwd();
|
||||
$path="../../interface/locale/";
|
||||
$directory = dir($path);
|
||||
$invalid_dir = array('CVS' => 1, '..' => 2, '.' => 3);
|
||||
$locales = array();
|
||||
$index = 0;
|
||||
|
||||
while ($arch = $directory->read())
|
||||
{
|
||||
if (!isset($invalid_dir[$arch]) && is_dir($path.$arch)){
|
||||
$locales[$index] = $arch;
|
||||
$index ++;
|
||||
}
|
||||
}
|
||||
|
||||
$directory->close();
|
||||
|
||||
asort($locales);
|
||||
foreach ($locales as $locale){
|
||||
echo $locale;
|
||||
$cmd = "./mergelocale.sh $locale";
|
||||
exec($cmd);
|
||||
}
|
||||
|
||||
exec( "php ./calc_l10n_stats.php" );
|
||||
?>
|
177
tools/i18n/mklocales.php
Normal file
177
tools/i18n/mklocales.php
Normal file
@ -0,0 +1,177 @@
|
||||
#!/usr/bin/php
|
||||
<?php
|
||||
/*********************************************************************************
|
||||
*
|
||||
* TimeTrex is a Workforce Management program developed by
|
||||
* TimeTrex Software Inc. Copyright (C) 2003 - 2021 TimeTrex Software Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License version 3 as published by
|
||||
* the Free Software Foundation with the addition of the following permission
|
||||
* added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
|
||||
* WORK IN WHICH THE COPYRIGHT IS OWNED BY TIMETREX, TIMETREX DISCLAIMS THE
|
||||
* WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program; if not, see http://www.gnu.org/licenses or write to the Free
|
||||
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA.
|
||||
*
|
||||
*
|
||||
* You can contact TimeTrex headquarters at Unit 22 - 2475 Dobbin Rd. Suite
|
||||
* #292 West Kelowna, BC V4T 2E9, Canada or at email address info@timetrex.com.
|
||||
*
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License
|
||||
* version 3, these Appropriate Legal Notices must retain the display of the
|
||||
* "Powered by TimeTrex" logo. If the display of the logo is not reasonably
|
||||
* feasible for technical reasons, the Appropriate Legal Notices must display
|
||||
* the words "Powered by TimeTrex".
|
||||
*
|
||||
********************************************************************************/
|
||||
/*
|
||||
* File Contributed By: Open Source Consulting, S.A. San Jose, Costa Rica.
|
||||
* http://osc.co.cr
|
||||
*/
|
||||
|
||||
if ( PHP_SAPI != 'cli' ) {
|
||||
echo "This script can only be called from the Command Line.\n";
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
// creates the locale directories for use with gettext
|
||||
// and also initializes each with a messages.po file.
|
||||
// Must be run from the i18n tools directory
|
||||
//
|
||||
|
||||
$depth = '../..';
|
||||
|
||||
$locales = [
|
||||
// 'af_ZA',
|
||||
// 'am_ET',
|
||||
'ar_EG',
|
||||
// 'as_IN',
|
||||
// 'az_AZ',
|
||||
// 'be_BY',
|
||||
// 'bg_BG',
|
||||
// 'bn_IN',
|
||||
// 'bo_CN',
|
||||
// 'br_FR',
|
||||
// 'bs_BA',
|
||||
// 'ca_ES',
|
||||
// 'ce_RU',
|
||||
// 'co_FR',
|
||||
// 'cs_CZ',
|
||||
// 'cy_GB',
|
||||
'da_DK',
|
||||
'de_DE',
|
||||
// 'dz_BT',
|
||||
// 'el_GR',
|
||||
'en_US',
|
||||
'es_ES',
|
||||
// 'et_EE',
|
||||
// 'fa_IR',
|
||||
// 'fi_FI',
|
||||
// 'fj_FJ',
|
||||
// 'fo_FO',
|
||||
'fr_FR',
|
||||
'fr_CA',
|
||||
// 'ga_IE',
|
||||
// 'gd_GB',
|
||||
// 'gu_IN',
|
||||
// 'he_IL',
|
||||
// 'hi_IN',
|
||||
// 'hr_HR',
|
||||
'hu_HU',
|
||||
// 'hy_AM',
|
||||
'id_ID',
|
||||
// 'is_IS',
|
||||
'it_IT',
|
||||
// 'ja_JP',
|
||||
// 'jv_ID',
|
||||
// 'ka_GE',
|
||||
// 'kk_KZ',
|
||||
// 'kl_GL',
|
||||
// 'km_KH',
|
||||
// 'kn_IN',
|
||||
// 'ko_KR',
|
||||
// 'kok_IN',
|
||||
// 'lo_LA',
|
||||
// 'lt_LT',
|
||||
// 'lv_LV',
|
||||
// 'mg_MG',
|
||||
// 'mk_MK',
|
||||
// 'ml_IN',
|
||||
// 'mn_MN',
|
||||
// 'mr_IN',
|
||||
// 'ms_MY',
|
||||
// 'mt_MT',
|
||||
// 'my_MM',
|
||||
// 'mni_IN',
|
||||
// 'na_NR',
|
||||
// 'nb_NO',
|
||||
// 'ne_NP',
|
||||
// 'nl_NL',
|
||||
// 'nn_NO',
|
||||
// 'no_NO',
|
||||
// 'oc_FR',
|
||||
// 'or_IN',
|
||||
// 'pa_IN',
|
||||
// 'pl_PL',
|
||||
// 'ps_AF',
|
||||
'pt_PT',
|
||||
'pt_BR',
|
||||
// 'rm_CH',
|
||||
// 'rn_BI',
|
||||
'ro_RO',
|
||||
// 'ru_RU',
|
||||
// 'sa_IN',
|
||||
// 'sc_IT',
|
||||
// 'sg_CF',
|
||||
// 'si_LK',
|
||||
// 'sk_SK',
|
||||
// 'sl_SI',
|
||||
// 'so_SO',
|
||||
// 'sq_AL',
|
||||
// 'sr_YU',
|
||||
// 'sv_SE',
|
||||
// 'te_IN',
|
||||
// 'tg_TJ',
|
||||
// 'th_TH',
|
||||
// 'tk_TM',
|
||||
// 'tl_PH',
|
||||
// 'to_TO',
|
||||
// 'tr_TR',
|
||||
// 'uk_UA',
|
||||
// 'ur_PK',
|
||||
// 'uz_UZ',
|
||||
// 'vi_VN',
|
||||
// 'wa_BE',
|
||||
// 'wen_DE',
|
||||
// 'lp_SG',
|
||||
'zh_ZH',
|
||||
'yi_US',
|
||||
];
|
||||
|
||||
$dir = $depth . '/interface/locale';
|
||||
chdir( $dir );
|
||||
|
||||
foreach ( $locales as $locale ) {
|
||||
if ( !is_dir( './' . $locale ) ) {
|
||||
$cmd = "mkdir $locale && mkdir $locale/LC_MESSAGES && msginit --no-translator -l $locale -o $locale/LC_MESSAGES/messages.po -i messages.pot";
|
||||
shell_exec( $cmd );
|
||||
}
|
||||
}
|
||||
?>
|
325
tools/i18n/notify_translator.php
Normal file
325
tools/i18n/notify_translator.php
Normal file
@ -0,0 +1,325 @@
|
||||
#!/usr/bin/php
|
||||
<?php
|
||||
/*********************************************************************************
|
||||
*
|
||||
* TimeTrex is a Workforce Management program developed by
|
||||
* TimeTrex Software Inc. Copyright (C) 2003 - 2021 TimeTrex Software Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License version 3 as published by
|
||||
* the Free Software Foundation with the addition of the following permission
|
||||
* added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
|
||||
* WORK IN WHICH THE COPYRIGHT IS OWNED BY TIMETREX, TIMETREX DISCLAIMS THE
|
||||
* WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program; if not, see http://www.gnu.org/licenses or write to the Free
|
||||
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA.
|
||||
*
|
||||
*
|
||||
* You can contact TimeTrex headquarters at Unit 22 - 2475 Dobbin Rd. Suite
|
||||
* #292 West Kelowna, BC V4T 2E9, Canada or at email address info@timetrex.com.
|
||||
*
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License
|
||||
* version 3, these Appropriate Legal Notices must retain the display of the
|
||||
* "Powered by TimeTrex" logo. If the display of the logo is not reasonably
|
||||
* feasible for technical reasons, the Appropriate Legal Notices must display
|
||||
* the words "Powered by TimeTrex".
|
||||
*
|
||||
********************************************************************************/
|
||||
/*
|
||||
* File Contributed By: Open Source Consulting, S.A. San Jose, Costa Rica.
|
||||
* http://osc.co.cr
|
||||
*/
|
||||
|
||||
if ( PHP_SAPI != 'cli' ) {
|
||||
echo "This script can only be called from the Command Line.\n";
|
||||
exit;
|
||||
}
|
||||
|
||||
// This script will send an email notification to the translator(s) listed in any
|
||||
// messages.po file. The notification basically says that the application has
|
||||
// been updated and it would be great if they can review/update the translations.
|
||||
|
||||
// This script is intended to be run by hand. It is important that one person be in
|
||||
// charge of this, as we do not want to be spamming people over and over.
|
||||
|
||||
$root_dir = '../../interface/locale';
|
||||
if ( count( $argv ) > 1 ) {
|
||||
$root_dir = $argv[1];
|
||||
}
|
||||
|
||||
$d = opendir( $root_dir );
|
||||
|
||||
if ( $d ) {
|
||||
$outpath = $root_dir . '/' . 'statistics.txt';
|
||||
$fh = fopen( $outpath, 'w' );
|
||||
|
||||
$ignore_dirs = [ '.', '..', 'CVS' ];
|
||||
while ( false !== ( $file = readdir( $d ) ) ) {
|
||||
if ( is_dir( $root_dir . '/' . $file ) && !in_array( $file, $ignore_dirs ) ) {
|
||||
$stats = calcStats( $root_dir, $file );
|
||||
$pct = $stats['pct_complete'];
|
||||
$team = $stats['team'];
|
||||
$trans = $stats['translator'];
|
||||
|
||||
$string_lang = lang( $file );
|
||||
|
||||
$emailTeam = search_email( $team );
|
||||
if ( $emailTeam != null ) {
|
||||
send_mail( $emailTeam, 'timetrex', $string_lang, $pct );
|
||||
}
|
||||
|
||||
$emailTrans = search_email( $trans );
|
||||
if ( $emailTrans != null ) {
|
||||
send_mail( $emailTrans, 'timetrex', $string_lang, $pct );
|
||||
}
|
||||
fwrite( $fh, "$file|$pct|$team|$trans|$string_lang\n" );
|
||||
}
|
||||
}
|
||||
closedir( $d );
|
||||
|
||||
fclose( $fh );
|
||||
}
|
||||
|
||||
function calcStats( $root_dir, $locale ) {
|
||||
$messages = 0;
|
||||
$translations = 0;
|
||||
$fuzzy = 0;
|
||||
|
||||
$team = '';
|
||||
$trans = '';
|
||||
|
||||
$path = $root_dir . '/' . $locale . '/LC_MESSAGES/messages.po';
|
||||
// echo "<li><b>$path</b>";
|
||||
if ( file_exists( $path ) ) {
|
||||
$lines = file( $path );
|
||||
|
||||
$in_msgid = false;
|
||||
$in_msgstr = false;
|
||||
$found_translation = false;
|
||||
$found_msg = false;
|
||||
|
||||
|
||||
foreach ( $lines as $line ) {
|
||||
// ignore comment lines
|
||||
if ( $line[0] == '#' ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse out the contributors.
|
||||
if ( strstr( $line, '"Language-Team: ' ) ) {
|
||||
$endpos = strpos( $line, '\n' );
|
||||
if ( $endpos === false ) {
|
||||
$endpos = strlen( $line ) - 2;
|
||||
}
|
||||
$len = strlen( '"Language-Team: ' );
|
||||
$field = substr( $line, $len, $endpos - $len );
|
||||
$names = explode( ',', $field );
|
||||
foreach ( $names as $name ) {
|
||||
if ( $name != 'none' ) {
|
||||
if ( $team != '' ) {
|
||||
$team .= ',';
|
||||
}
|
||||
$team .= trim( $name );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Parse the Last-Translator
|
||||
if ( strstr( $line, '"Last-Translator: ' ) ) {
|
||||
$endpos = strpos( $line, '\n' );
|
||||
if ( $endpos === false ) {
|
||||
$endpos = strlen( $line ) - 2;
|
||||
}
|
||||
$len = strlen( '"Last-Translator: ' );
|
||||
$field = substr( $line, $len, $endpos - $len );
|
||||
$Transnames = explode( ',', $field );
|
||||
foreach ( $Transnames as $Transname ) {
|
||||
if ( $Transname != 'Automatically generated' ) {
|
||||
if ( $trans != '' ) {
|
||||
$trans .= ',';
|
||||
}
|
||||
$trans .= trim( $Transname );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( strstr( $line, 'msgid "' ) ) {
|
||||
$in_msgid = true;
|
||||
$in_msgstr = false;
|
||||
$found_msg = false;
|
||||
$found_translation = false;
|
||||
}
|
||||
if ( $in_msgid && !$found_msg && strstr( $line, '"' ) && !strstr( $line, '""' ) ) {
|
||||
// echo "<li>msgid: $line";
|
||||
$found_msg = true;
|
||||
$messages++;
|
||||
} else if ( strstr( $line, 'msgstr "' ) ) {
|
||||
$in_msgstr = true;
|
||||
$in_msgid = false;
|
||||
}
|
||||
if ( $in_msgstr && $found_msg && !$found_translation ) {
|
||||
if ( strstr( $line, '"' ) && !strstr( $line, '""' ) ) {
|
||||
// echo "<li>msgstr: $line";
|
||||
$translations++;
|
||||
$found_translation = true;
|
||||
}
|
||||
} else if ( strstr( $line, '#, fuzzy' ) ) {
|
||||
$fuzzy++;
|
||||
}
|
||||
}
|
||||
}
|
||||
$translations -= $fuzzy;
|
||||
$pct_complete = $messages ? (int)( ( $translations / $messages ) * 100 ) : 0;
|
||||
|
||||
return [ 'pct_complete' => $pct_complete, 'team' => $team, 'translator' => $trans ];
|
||||
}
|
||||
|
||||
function statistics( $cad ) {
|
||||
$names = explode( ',', $cad );
|
||||
foreach ( $names as $name ) {
|
||||
if ( strstr( $name, ' translated' ) ) {
|
||||
$translated = $name;
|
||||
} else {
|
||||
if ( strstr( $name, ' fuzzy' ) ) {
|
||||
$fuzzy = $name;
|
||||
} else {
|
||||
if ( strstr( $name, ' untranslated' ) ) {
|
||||
$untranslated = $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [ 'translated' => $translated, 'fuzzy' => $fuzzy, 'untranslated' => $untranslated ];
|
||||
}
|
||||
|
||||
function send_mail( $to, $from, $lang, $pct ) {
|
||||
$subject = <<<END
|
||||
Timetrex updated, {$lang} translation out of date
|
||||
END;
|
||||
|
||||
$msg = <<<END
|
||||
to: {$to}
|
||||
from: {$from}
|
||||
subject: {$subject}
|
||||
|
||||
Hello,
|
||||
|
||||
You are receiving this email because you previously contributed {$lang}
|
||||
translations to Timetrex. The application has recently been updated, which
|
||||
means that new english text needs to be translated.
|
||||
|
||||
As of this moment, the {$lang} translation file is {$pct}% complete.
|
||||
|
||||
If you could find a few minutes to update the translation file, it would be
|
||||
greatly appreciated. In any event, we thank-you for your previous contribution,
|
||||
which has helped the site reach many {$lang} readers.
|
||||
|
||||
You can always find the latest language files and instructions at:
|
||||
|
||||
*TODO_NEED_URL_HERE*
|
||||
|
||||
Best,
|
||||
|
||||
Timetrex
|
||||
END;
|
||||
|
||||
$msg = wordwrap( $msg, 100 );
|
||||
$subject = wordwrap( $subject, 100 );
|
||||
mail( $to, $subject, $msg );
|
||||
}
|
||||
|
||||
function lang( $cad ) {
|
||||
$languages = [
|
||||
'af_ZA' => 'Afrikaans', 'am_ET' => 'Amharic', 'as_IN' => 'Assamese', 'az_AZ' => 'Azerbaijani',
|
||||
'be_BY' => 'Belarusian', 'bg_BG' => 'Bulgarian', 'bn_IN' => 'Bengali', 'bo_CN' => 'Tibetan',
|
||||
'br_FR' => 'Breton', 'bs_BA' => 'Bosnian', 'ca_ES' => 'Catalan', 'ce_RU' => 'Chechen', 'co_FR' => 'Corsican',
|
||||
'cs_CZ' => 'Czech', 'cy_GB' => 'Welsh', 'da_DK' => 'Danish', 'de_DE' => 'German', 'dz_BT' => 'Dzongkha', 'el_GR' => 'Greek',
|
||||
'en_US' => 'English', 'es_ES' => 'Spanish', 'et_EE' => 'Estonian', 'fa_IR' => 'Persian', 'fi_FI' => 'Finnish',
|
||||
'fj_FJ' => 'Fijian', 'fo_FO' => 'Faroese', 'fr_FR' => 'French', 'ga_IE' => 'Irish', 'gd_GB' => 'Scots',
|
||||
'gu_IN' => 'Gujarati', 'he_IL' => 'Hebrew', 'hi_IN' => 'Hindi', 'hr_HR' => 'Croatian', 'hu_HU' => 'Hungarian',
|
||||
'hy_AM' => 'Armenian', 'id_ID' => 'Indonesian', 'is_IS' => 'Icelandic', 'it_IT' => 'Italian', 'ja_JP' => 'Japanese',
|
||||
'jv_ID' => 'jv', 'ka_GE' => 'GeorgKoreanian', 'kk_KZ' => 'Kazakh', 'kl_GL' => 'Kalaallisut', 'km_KH' => 'Khmer',
|
||||
'kn_IN' => 'Kannada', 'kok_IN' => 'Konkani', 'ko_KR' => 'Korean', 'lo_LA' => 'Laotian', 'lt_LT' => 'Lithuanian',
|
||||
'lv_LV' => 'Latvian', 'mg_MG' => 'Malagasy', 'mk_MK' => 'Macedonian', 'ml_IN' => 'Malayalam', 'mni_IN' => 'Manipuri',
|
||||
'mn_MN' => 'Mongolian', 'mr_IN' => 'Marathi', 'ms_MY' => 'Malay', 'mt_MT' => 'Maltese', 'my_MM' => 'Burmese',
|
||||
'na_NR' => 'Nauru', 'nb_NO' => 'Norwegian', 'ne_NP' => 'Nepali', 'nl_NL' => 'Dutch', 'nn_NO' => 'Norwegian',
|
||||
'oc_FR' => 'Occitan', 'or_IN' => 'Oriya', 'pa_IN' => 'Punjabi', 'pl_PL' => 'Polish', 'ps_AF' => 'Pashto', 'pt_PT' => 'Portuguese',
|
||||
'rm_CH' => 'Rhaeto-Roman', 'rn_BI' => 'Kirundi', 'ro_RO' => 'Romanian', 'ru_RU' => 'Russian', 'sa_IN' => 'Sanskrit',
|
||||
'sc_IT' => 'Sardinian', 'sg_CF' => 'Sango', 'si_LK' => 'Sinhalese', 'sk_SK' => 'Slovak', 'sl_SI' => 'Slovenian',
|
||||
'so_SO' => 'Somali', 'sq_AL' => 'Albanian', 'sr_YU' => 'Serbian', 'sv_SE' => 'Swedish', 'te_IN' => 'Telugu',
|
||||
'tg_TJ' => 'Tajik', 'th_TH' => 'Thai', 'tk_TM' => 'Turkmen', 'tl_PH' => 'Tagalog', 'to_TO' => 'Tonga', 'tr_TR' => 'Turkish',
|
||||
'uk_UA' => 'Ukrainian', 'ur_PK' => 'Urdu', 'uz_UZ' => 'Uzbek', 'vi_VN' => 'Vietnamese', 'wa_BE' => 'wa', 'wen_DE' => 'Sorbian',
|
||||
];
|
||||
|
||||
return ( $languages[$cad] );
|
||||
}
|
||||
|
||||
function search_email( $cad ) {
|
||||
$names = '';
|
||||
$i = 0;
|
||||
$name = '';
|
||||
|
||||
$names = explode( ' ', $cad );
|
||||
foreach ( $names as $name ) {
|
||||
for ( $i = 0; $i <= strlen( $name ); $i++ ) {
|
||||
if ( $name[$i] == "<" || $name[$i] == ">" ) {
|
||||
$name[$i] = " ";
|
||||
}
|
||||
}
|
||||
if ( check_email_address( trim( $name ) ) ) {
|
||||
return ( $name );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function check_email_address( $email ) {
|
||||
// check @
|
||||
if ( !ereg( "[^@]{1,64}@[^@]{1,255}", $email ) ) {
|
||||
// Email error @'s
|
||||
return false;
|
||||
}
|
||||
|
||||
$email_array = explode( "@", $email );
|
||||
$local_array = explode( ".", $email_array[0] );
|
||||
for ( $i = 0; $i < sizeof( $local_array ); $i++ ) {
|
||||
if ( !ereg( "^(([A-Za-z0-9!#$%&'*+/=?^_`{|}~-][A-Za-z0-9!#$%&'*+/=?^
|
||||
_`{|}~\.-]{0,63})|(\"[^(\\|\")]{0,62}\"))$", $local_array[$i] ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ( !ereg( "^\[?[0-9\.]+\]?$", $email_array[1] ) ) {
|
||||
// check domain
|
||||
$domain_array = explode( ".", $email_array[1] );
|
||||
|
||||
if ( sizeof( $domain_array ) < 2 ) {
|
||||
return false; // Domain false
|
||||
}
|
||||
for ( $i = 0; $i < sizeof( $domain_array ); $i++ ) {
|
||||
if ( !ereg( "^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|([A-Za-z0-9]+))$", $domain_array[$i] ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
?>
|
179
tools/i18n/po2json.php
Normal file
179
tools/i18n/po2json.php
Normal file
@ -0,0 +1,179 @@
|
||||
<?php
|
||||
if ( PHP_SAPI != 'cli' ) {
|
||||
echo "This script can only be called from the Command Line.\n";
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* String object
|
||||
*/
|
||||
class PoeditString {
|
||||
public $key;
|
||||
public $value;
|
||||
public $fuzzy;
|
||||
public $comments;
|
||||
|
||||
function __construct( $key, $value = '', $fuzzy = false, $comments = [] ) {
|
||||
$this->key = $key;
|
||||
$this->value = $value;
|
||||
$this->fuzzy = $fuzzy;
|
||||
$this->comments = (array)$comments;
|
||||
}
|
||||
|
||||
public function __toString() {
|
||||
$str = '';
|
||||
foreach ( $this->comments as $c ) {
|
||||
$str .= "#: $c\n";
|
||||
}
|
||||
if ( $this->fuzzy ) {
|
||||
$str .= "#, fuzzy\n";
|
||||
}
|
||||
$str .= 'msgid "' . str_replace( '"', '\\"', $this->key ) . '"' . "\n";
|
||||
$str .= 'msgstr "' . str_replace( '"', '\\"', $this->value ) . '"' . "\n";
|
||||
$str .= "\n";
|
||||
|
||||
return $str;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parser object
|
||||
*/
|
||||
class PoeditParser {
|
||||
|
||||
protected $file;
|
||||
protected $header = '';
|
||||
protected $strings = [];
|
||||
|
||||
protected function _fixQuotes( $str ) {
|
||||
return stripslashes( $str );
|
||||
}
|
||||
|
||||
public function __construct( $file ) {
|
||||
$this->file = $file;
|
||||
}
|
||||
|
||||
public function parse() {
|
||||
$contents = file_get_contents( $this->file );
|
||||
$parts = preg_split( '#(\r\n|\n){2}#', $contents, -1, PREG_SPLIT_NO_EMPTY );
|
||||
$this->header = array_shift( $parts );
|
||||
|
||||
foreach ( $parts as $part ) {
|
||||
|
||||
// parse comments
|
||||
$comments = [];
|
||||
preg_match_all( '#^\\#: (.*?)$#m', $part, $matches, PREG_SET_ORDER );
|
||||
foreach ( $matches as $m ) {
|
||||
$comments[] = $m[1];
|
||||
}
|
||||
|
||||
$isFuzzy = preg_match( '#^\\#, fuzzy$#im', $part ) ? true : false;
|
||||
|
||||
preg_match_all( '# ^ (msgid|msgstr)\ " ( (?: (?>[^"\\\\]++) | \\\\\\\\ | (?<!\\\\)\\\\(?!\\\\) | \\\\" )* ) (?<!\\\\)" $ #ixm', $part, $matches2, PREG_SET_ORDER );
|
||||
$k = null;
|
||||
if ( isset( $matches2[0][2] ) ) {
|
||||
$k = $this->_fixQuotes( $matches2[0][2] );
|
||||
}
|
||||
|
||||
$v = !empty( $matches2[1][2] ) ? $this->_fixQuotes( $matches2[1][2] ) : '';
|
||||
|
||||
$this->strings[$k] = new PoeditString( $k, $v, $isFuzzy, $comments );
|
||||
}
|
||||
}
|
||||
|
||||
public function merge( $strings ) {
|
||||
foreach ( (array)$strings as $str ) {
|
||||
if ( !in_array( $str, array_keys( $this->strings ) ) ) {
|
||||
$this->strings[$str] = new PoeditString( $str );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getHeader() {
|
||||
return $this->header;
|
||||
}
|
||||
|
||||
public function getStrings() {
|
||||
return $this->strings;
|
||||
}
|
||||
|
||||
public function getJSON() {
|
||||
$str = [];
|
||||
foreach ( $this->strings as $s ) {
|
||||
echo "String: Key: " . $s->key . " Value: " . $s->value . "\n";
|
||||
if ( $s->value /*&& strlen($s->value) > 0*/ ) {
|
||||
$str[$s->key] = $s->value;
|
||||
} else {
|
||||
//$str[$s->key] = $s->key; //Don't export strings that haven't been translated to save space.
|
||||
}
|
||||
}
|
||||
|
||||
return json_encode( $str );
|
||||
}
|
||||
|
||||
public function toJSON( $outputFilename, $varName = 'l10n' ) {
|
||||
$str = "$varName = " . $this->getJSON();
|
||||
|
||||
return file_put_contents( $outputFilename, $str ) !== false;
|
||||
}
|
||||
|
||||
public function save( $filename = null ) {
|
||||
$data = $this->header . "\n\n";
|
||||
foreach ( $this->strings as $str ) {
|
||||
$data .= $str;
|
||||
}
|
||||
|
||||
return file_put_contents( $filename ? $filename : $this->file, $data ) !== false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param unknown_type $args
|
||||
*/
|
||||
function buildOptions( $args ) {
|
||||
$options = [
|
||||
'-o' => null,
|
||||
'-i' => null,
|
||||
'-n' => 'l10n',
|
||||
];
|
||||
$len = count( $args );
|
||||
$i = 0;
|
||||
while ( $i < $len ) {
|
||||
if ( preg_match( '#^-[a-z]$#i', $args[$i] ) ) {
|
||||
$options[$args[$i]] = isset( $args[$i + 1] ) ? trim( $args[$i + 1] ) : true;
|
||||
$i += 2;
|
||||
} else {
|
||||
$options[] = $args[$i];
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Script entry point
|
||||
*
|
||||
* Usage :
|
||||
* =======
|
||||
* php po2json -i <path/to/file.po> -o <path/to/file.json> {optional} -n <variable name (default is l10n)>
|
||||
*
|
||||
* This script is based on the project jsgettext : http://code.google.com/p/jsgettext/
|
||||
* I've updated it slightly to meet my need
|
||||
*/
|
||||
$options = buildOptions( $argv );
|
||||
|
||||
if ( !file_exists( $options['-i'] ) || !is_readable( $options['-i'] ) ) {
|
||||
die( "Invalid input file. Make sure it exists and is readable." . "\n" );
|
||||
}
|
||||
|
||||
$poeditParser = new PoeditParser( $options['-i'] );
|
||||
$poeditParser->parse();
|
||||
|
||||
if ( $poeditParser->toJSON( $options['-o'], $options['-n'] ) ) {
|
||||
$strings = count( $poeditParser->getStrings() );
|
||||
echo "Successfully exported " . $strings . " strings.\n";
|
||||
} else {
|
||||
echo "Cannor write to file '{$options['-o']}'.\n";
|
||||
}
|
174
tools/i18n/translate.php
Normal file
174
tools/i18n/translate.php
Normal file
@ -0,0 +1,174 @@
|
||||
<?php
|
||||
require_once( dirname( __FILE__ ) . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'includes' . DIRECTORY_SEPARATOR . 'global.inc.php' );
|
||||
require_once( dirname( __FILE__ ) . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'includes' . DIRECTORY_SEPARATOR . 'CLI.inc.php' );
|
||||
|
||||
use Gettext\Loader\PoLoader;
|
||||
use Gettext\Generator\PoGenerator;
|
||||
|
||||
/*
|
||||
* This script when passed a gettext *.POT, or *.PO file will
|
||||
* attempt to use a online service to translate each string
|
||||
* to the specified language.
|
||||
*
|
||||
* This should hopefully serve as a good STARTING point for further
|
||||
* human transation.
|
||||
*
|
||||
* This file will first create batched input files ready for translation.
|
||||
* It will then load the translated files and create a messages.po file from them.
|
||||
*
|
||||
* Take .PO file and create small HTML batch files for translations
|
||||
* php translate.php -s ../../interface/locale/fr_FR/LC_MESSAGES/messages.po ./tr_batches.html
|
||||
*
|
||||
* Using a web browser to translate the .html file, scroll all the way to the bottom of the file, save it from the Dev Tools.
|
||||
*
|
||||
* Translate HTML batch files back into .PO file
|
||||
* php translate.php -t ../../interface/locale/fr_FR/LC_MESSAGES/messages.po ./tr_batches.html1 fr.po
|
||||
*
|
||||
*/
|
||||
if ( PHP_SAPI != 'cli' ) {
|
||||
echo "This script can only be called from the Command Line.\n";
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( $argc < 3 || in_array( $argv[1], [ '--help', '-help', '-h', '-?' ] ) ) {
|
||||
$help_output = "Usage: translate.php [OPTIONS] \n";
|
||||
$help_output .= " Options:\n";
|
||||
$help_output .= " -s [.POT or .PO] [OUT HTML]\n";
|
||||
$help_output .= " Create a source translation file, suitable to be translated on mass.\n";
|
||||
$help_output .= " -t [.POT or .PO] [IN HTML] [OUTFILE]\n";
|
||||
|
||||
echo $help_output;
|
||||
} else {
|
||||
//Handle command line arguments
|
||||
$last_arg = count( $argv ) - 1;
|
||||
|
||||
if ( in_array( '-s', $argv ) ) {
|
||||
$create_source = true;
|
||||
} else {
|
||||
$create_source = false;
|
||||
}
|
||||
|
||||
if ( isset( $argv[$last_arg - 2] ) && $argv[2] != '' ) {
|
||||
if ( !file_exists( $argv[2] ) || !is_readable( $argv[2] ) ) {
|
||||
echo ".POT or .PO File: " . $argv[2] . " does not exists or is not readable!\n";
|
||||
} else {
|
||||
$source_file = $argv[2];
|
||||
}
|
||||
|
||||
if ( $create_source == true ) {
|
||||
$outfile = $argv[3];
|
||||
$infile = null;
|
||||
} else {
|
||||
$infile = $argv[3];
|
||||
$outfile = $argv[4];
|
||||
}
|
||||
echo "In File: $infile\n";
|
||||
echo "Out File: $outfile\n";
|
||||
|
||||
//import from a .po file:
|
||||
$po = new PoLoader();
|
||||
$po_strings = $po->loadFile( $source_file );
|
||||
|
||||
if ( $create_source == true ) {
|
||||
$batch_size = 1000;
|
||||
//$batch_size = 999999;
|
||||
$batch = 0;
|
||||
$prev_batch = 0;
|
||||
$i = 0;
|
||||
$out = null;
|
||||
$max = count( $po_strings->getTranslations() ) - 1;
|
||||
echo "Max: $max\n";
|
||||
foreach ( $po_strings->getTranslations() as $msg_obj ) {
|
||||
//echo "$i. $msgid\n";
|
||||
$msgid = preg_replace('/[\x00-\x1F\x7F]/', '', $msg_obj->getId() );
|
||||
$msgstr = trim( $msg_obj->getTranslation() );
|
||||
if ( $msgid == '#' || $msgstr != '' ) {
|
||||
$i++;
|
||||
|
||||
if ( $i < $max ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $i == 0 || $out == null ) {
|
||||
echo "I = 0 OR Batch = 0\n";
|
||||
$out = "<html>\n";
|
||||
$out .= "<body><pre>\n";
|
||||
}
|
||||
|
||||
if ( $i > 0 && ( $i % $batch_size == 0 || $i == $max ) ) {
|
||||
$batch++;
|
||||
echo "New Batch = $batch\n";
|
||||
}
|
||||
|
||||
$out .= '<span class="' . htmlentities( $msgid ) . '">' . htmlentities( $msgid ) . "</span><br>\n";
|
||||
//$out .= $i.': '. str_replace('<br>', '(11)', $msgid) ."<br>\n";
|
||||
|
||||
if ( $batch != $prev_batch ) {
|
||||
echo "Writing...\n";
|
||||
$out .= "</pre></body>\n";
|
||||
$out .= "</html>\n";
|
||||
|
||||
//Write the file.
|
||||
$output_file_name = str_replace( '.', '-'. $batch .'.', dirname( $outfile ) . DIRECTORY_SEPARATOR . basename( $outfile ) );
|
||||
echo "Writing to: ". $output_file_name ."\n";
|
||||
file_put_contents( $output_file_name, $out );
|
||||
|
||||
$out = null;
|
||||
}
|
||||
|
||||
$prev_batch = $batch;
|
||||
$i++;
|
||||
}
|
||||
} else {
|
||||
//Load translated HTML files.
|
||||
echo "Loading Translated File\n";
|
||||
|
||||
$file_contents = file_get_contents( $infile );
|
||||
$file_contents = preg_replace( '/<html .*>/iu', '', $file_contents );
|
||||
$file_contents = preg_replace( '/<head>.*<\/head>/iu', '', $file_contents );
|
||||
$file_contents = preg_replace( '/<base.*>/iu', '', $file_contents );
|
||||
$file_contents = preg_replace( '/<\/span>([\s]*)<br>/iu', '</span>', $file_contents );
|
||||
$file_contents = preg_replace( '/<\/span><br>([\s]*)/iu', '</span>', $file_contents );
|
||||
$file_contents = preg_replace( '/<font style="(.*)">/iu', '', $file_contents );
|
||||
$file_contents = preg_replace( '/ :/iu', ':', $file_contents );
|
||||
$file_contents = str_replace( [ '<html>', '</html>', '<body>', '</body>', '<pre>', '</pre>', '</font>' ], '', $file_contents );
|
||||
|
||||
$lines = explode( '</span>', $file_contents );
|
||||
//var_dump($lines);
|
||||
if ( is_array( $lines ) ) {
|
||||
echo "Total Lines: " . count( $lines ) . "\n";
|
||||
|
||||
$i = 0;
|
||||
foreach ( $lines as $line ) {
|
||||
//Parse the string number
|
||||
if ( preg_match( '/<span class=\"(.*)\">(.*)/i', trim( $line ), $matches ) == true ) {
|
||||
if ( is_array( $matches ) && isset( $matches[1] ) && isset( $matches[2] ) ) {
|
||||
$msgid = html_entity_decode( $matches[1] );
|
||||
$msgstr = preg_replace( '/\s\"\s/iu', '"', html_entity_decode( $matches[2] ) );
|
||||
|
||||
echo $i . ". Translating: " . $msgid . "\n";
|
||||
echo " To: " . $msgstr . "\n";
|
||||
$tmp_translation = $po_strings->find( null, $msgid );
|
||||
if ( $tmp_translation ) {
|
||||
$tmp_translation->translate( $msgstr );
|
||||
} else {
|
||||
echo "Failed to find translation key...\n";
|
||||
}
|
||||
} else {
|
||||
echo "ERROR parsing line!\n";
|
||||
}
|
||||
} else {
|
||||
echo "Failed to match line!\n";
|
||||
}
|
||||
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
|
||||
$po_generator = new PoGenerator();
|
||||
$po_generator->generateFile( $po_strings, $outfile );
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
128
tools/i18n/tsmarty2c.php
Normal file
128
tools/i18n/tsmarty2c.php
Normal file
@ -0,0 +1,128 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
/**
|
||||
* tsmarty2c.php - rips gettext strings from smarty template
|
||||
*
|
||||
* ------------------------------------------------------------------------- *
|
||||
* This library is free software; you can redistribute it and/or *
|
||||
* modify it under the terms of the GNU Lesser General Public *
|
||||
* License as published by the Free Software Foundation; either *
|
||||
* version 2.1 of the License, or (at your option) any later version. *
|
||||
* *
|
||||
* This library is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
||||
* Lesser General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Lesser General Public *
|
||||
* License along with this library; if not, write to the Free Software *
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *
|
||||
* ------------------------------------------------------------------------- *
|
||||
*
|
||||
* This command line script rips gettext strings from smarty file,
|
||||
* and prints them to stdout in C format, that can later be used with the
|
||||
* standard gettext tools.
|
||||
*
|
||||
* Usage:
|
||||
* ./tsmarty2c.php <filename or directory> <file2> <..> > smarty.c
|
||||
*
|
||||
* If a parameter is a directory, the template files within will be parsed.
|
||||
*
|
||||
* @package smarty-gettext
|
||||
* @version $Id$
|
||||
* @link http://smarty-gettext.sf.net/
|
||||
* @author Sagi Bashari <sagi@boom.org.il>
|
||||
* @copyright 2004-2005 Sagi Bashari
|
||||
*/
|
||||
|
||||
if ( PHP_SAPI != 'cli' ) {
|
||||
echo "This script can only be called from the Command Line.\n";
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
// smarty open tag
|
||||
$ldq = preg_quote( '{' );
|
||||
|
||||
// smarty close tag
|
||||
$rdq = preg_quote( '}' );
|
||||
|
||||
// smarty command
|
||||
$cmd = preg_quote( 't' );
|
||||
|
||||
// extensions of smarty files, used when going through a directory
|
||||
$extensions = [ 'tpl' ];
|
||||
|
||||
// "fix" string - strip slashes, escape and convert new lines to \n
|
||||
function fs( $str ) {
|
||||
$str = stripslashes( $str );
|
||||
$str = str_replace( '"', '\"', $str );
|
||||
$str = str_replace( "\n", '\n', $str );
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
// rips gettext strings from $file and prints them in C format
|
||||
function do_file( $file ) {
|
||||
$content = @file_get_contents( $file );
|
||||
|
||||
if ( empty( $content ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
global $ldq, $rdq, $cmd;
|
||||
|
||||
preg_match_all(
|
||||
"/{$ldq}\s*({$cmd})\s*([^{$rdq}]*){$rdq}([^{$ldq}]*){$ldq}\/\\1{$rdq}/",
|
||||
$content,
|
||||
$matches
|
||||
);
|
||||
|
||||
for ( $i = 0; $i < count( $matches[0] ); $i++ ) {
|
||||
// TODO: add line number
|
||||
echo "/* $file */\n"; // credit: Mike van Lammeren 2005-02-14
|
||||
|
||||
if ( preg_match( '/plural\s*=\s*["\']?\s*(.[^\"\']*)\s*["\']?/', $matches[2][$i], $match ) ) {
|
||||
echo 'ngettext("' . fs( $matches[3][$i] ) . '","' . fs( $match[1] ) . '",x);' . "\n";
|
||||
} else {
|
||||
echo 'gettext("' . fs( $matches[3][$i] ) . '");' . "\n";
|
||||
}
|
||||
|
||||
echo "\n";
|
||||
}
|
||||
}
|
||||
|
||||
// go through a directory
|
||||
function do_dir( $dir ) {
|
||||
$d = dir( $dir );
|
||||
|
||||
while ( false !== ( $entry = $d->read() ) ) {
|
||||
if ( $entry == '.' || $entry == '..' ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$entry = $dir . '/' . $entry;
|
||||
|
||||
if ( is_dir( $entry ) ) { // if a directory, go through it
|
||||
do_dir( $entry );
|
||||
} else { // if file, parse only if extension is matched
|
||||
$pi = pathinfo( $entry );
|
||||
|
||||
if ( isset( $pi['extension'] ) && in_array( $pi['extension'], $GLOBALS['extensions'] ) ) {
|
||||
do_file( $entry );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$d->close();
|
||||
}
|
||||
|
||||
for ( $ac = 1; $ac < $_SERVER['argc']; $ac++ ) {
|
||||
if ( is_dir( $_SERVER['argv'][$ac] ) ) { // go through directory
|
||||
do_dir( $_SERVER['argv'][$ac] );
|
||||
} else { // do file
|
||||
do_file( $_SERVER['argv'][$ac] );
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
33
tools/i18n/updatetranslation.sh
Executable file
33
tools/i18n/updatetranslation.sh
Executable file
@ -0,0 +1,33 @@
|
||||
#!/bin/bash
|
||||
|
||||
##$License$##
|
||||
##
|
||||
# File Contributed By: Open Source Consulting, S.A. San Jose, Costa Rica.
|
||||
# http://osc.co.cr
|
||||
##
|
||||
|
||||
# this script is intended to aid with accepting incoming translations (from translators)
|
||||
# in a messages.po file and merging that file into an existing messages.po.
|
||||
# It then merges in any changes in the master .pot file and compiles a
|
||||
# .mo binary file.
|
||||
|
||||
# Usage:
|
||||
# ./updatestranslation.sh <locale> <path_to_po_file_from_translator>
|
||||
|
||||
# Example:
|
||||
# cd tools/i18n/
|
||||
# ./updatestranslation.sh es_ES /tmp/messages.po.es_ES
|
||||
|
||||
# This script is intended to be run from the tools/i18n directory.
|
||||
|
||||
LOCALE=$1
|
||||
NEW_MESSAGES=$2
|
||||
dir=`pwd`
|
||||
|
||||
cd ../../interface/locale/$LOCALE/LC_MESSAGES/
|
||||
msgmerge $NEW_MESSAGES messages.po > messages.po.new
|
||||
echo `pwd`
|
||||
mv messages.po.new messages.po
|
||||
|
||||
cd $dir
|
||||
./mergelocale.sh $LOCALE
|
Reference in New Issue
Block a user