====Localization proposal using gettext==== < 'fr_CH', %% ===Locale directory structure=== The ##locale## directory is structured as follows: %% locale/ locale/po <--contains the generic template file; must be copied to lang-specific directories for translation locale/po/messages.pot <--generic template file locale/en_US <--locale-specific locale/en_US/LC_MESSAGES <--holds lang-specific translations locale/en_US/LC_MESSAGES/en_US.po <--lang-specific template file, usually created by msginit locale/en_US/LC_MESSAGES/en_US.mo <--compiled translation file, usually created by msgfmt locale/de_DE locale/de_DE/LC_MESSAGES etc... %% ===Generating the gettext template (.pot) file=== Any time new translation macros (of the form ##T_(...)##) are added to the source code, a new gettext template file must be generated. There are several different gettext utilities that can be used to generate this file. GNU gettext command-line utility examples are used in this document, so we will be using the gettext command from the Wikka top-level directory: ##find ./ -name '*.php' | xargs xgettext -L PHP --force-po -kT_ -o locale/po/messages.pot## ===Creating language-specific template (.po) files=== If one does not already exist, create a new directory structure under ##locale/## using [[http://www.rfc-editor.org/rfc/bcp/bcp47.txt | BCP-47]] language tags ([[http://schneegans.de/lv/ | validator]]). For instance: ##mkdir -p locale/fr_CH/LC_MESSAGES## The GNU gettext command ##msginit## can then be invoked to copy the ##messages.pot## template file for use with the language to be translated: ## msginit --locale fr_CH --input locale/po/messages.pot --output-file locale/fr_CH/LC_MESSAGES/fr_CH.po## ===Creating translations=== Several utilities exist that can be used to modify .po files. Some of the available utilities are listed [[http://www.gnu.org/software/gettext/manual/gettext.html#Editing | here]]. The file can also be modified manually in a text editor. [More info needed? I really don't want this to become a translation how-to!] ===Compiling translations (.po->.mo files)=== Once translations in the .po file are complete, these must be compiled into a binary format for use by the PHP-gettext libraries. The GNU gettext ##msgfmt## can be used here: ## msgfmt -o locale/fr_CH/LC_MESSAGES/fr_CH.mo locale/fr_CH/LC_MESSAGES/fr_CH.po## If you are receiving multibyte errors when running this command, you will most likely have to manually edit the .po file, specifically the following line: ## "Content-Type: text/plain; charset=UTF-8\n" ## ===Merging translations=== [TBD] ===expandDefines.pl=== %% #! /usr/bin/perl -w # # expandDefines.pl: Expand defines, mark with T_() tag for gettext # processing # # Usage: expandDefines.pl # # Author: Brian Koontz Copyright 2010 # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # ##################################################################### use strict; # Create dictionary of defines appearing in lang file if(!$ARGV[0]) { die "Usage: $0 \n"; } my $lang = $ARGV[0]; my $langfile = "/cygdrive/c/wamp/www/wikka-gettext/lang/$lang/$lang.inc.php"; my %dict = (); my $lineno = 0; open(IN, "<$langfile") or die "Can't open $langfile for reading!"; while() { chomp; if(!/^.*\s+define\s*\((.*?)\)/) { next; } my @fields = split(/\s*,\s*/, $1); my $const = $fields[0]; # Restore other commas my $val = join(', ', @fields[1..$#fields]); $const =~ s/['"](.*?)['"]/$1/; $val =~ s/['](.*?)[']/$1/; $dict{$const} = $val; $lineno++; } print "Number of language constants: $lineno\n"; close IN; # Parse each .php file, replacing constants with expansion: T("..."). my @filelist = (); # Exclude these files from search my @exclude = ($langfile, '3rdparty', 'wikka.config.php'); # Exclude these strings from search my @excludestrings = ('DIRECTORY_SEPARATOR', 'defined', 'define'); # Exclude these strings from gettext-wrapping my @excludefromtranslation = ('class=', 'id=', 'name='); use File::Find; sub getFile { if($File::Find::name=~/.*.php$/ && !grep $File::Find::name=~/$_/, @exclude) { push(@filelist, $File::Find::name); } } # Get list of files find (\&getFile, "."); $lineno = 0; my $header = 0; foreach my $file(@filelist) { open(IN, "<$file") or die "Can't open $file for reading!"; open(OUT, ">$file.new") or die "Can't open $file.new for writing!"; my $search = "([A-Z0-9]+(_[A-Z0-9]+)+)"; while() { $lineno++; my $line = $_; if(!grep($line=~/$_/, @excludestrings)) { # Search for two or more consecutive upper-case letter groupings # separated by _ while($line =~ /$search/g) { if(exists($dict{$1})) { if($dict{$1} =~ /^[0-9]+$/) { 1; # Don't do anything with defines # for numeric constants } elsif(!grep($dict{$1}=~/$_/, @excludefromtranslation)) { $line =~ s/$search/T_("$dict{$1}")/; } else { $line =~ s/$search/'$dict{$1}'/; } } } } else { 1; } print OUT $line; } close IN; close OUT; system("cp $file $file.orig"); system("cp $file.new $file"); $lineno = 0; $header = 0; } %%