| 1 |
PHP-gettext 1.0 |
|---|
| 2 |
|
|---|
| 3 |
Copyright 2003, 2006 -- Danilo "angry with PHP[1]" Segan |
|---|
| 4 |
Licensed under GPLv2 (or any later version, see COPYING) |
|---|
| 5 |
|
|---|
| 6 |
[1] PHP is actually cyrillic, and translates roughly to |
|---|
| 7 |
"works-doesn't-work" (UTF-8: РаЎО-Ðе-РаЎО) |
|---|
| 8 |
|
|---|
| 9 |
|
|---|
| 10 |
Introduction |
|---|
| 11 |
|
|---|
| 12 |
How many times did you look for a good translation tool, and |
|---|
| 13 |
found out that gettext is best for the job? Many times. |
|---|
| 14 |
|
|---|
| 15 |
How many times did you try to use gettext in PHP, but failed |
|---|
| 16 |
miserably, because either your hosting provider didn't support |
|---|
| 17 |
it, or the server didn't have adequate locale? Many times. |
|---|
| 18 |
|
|---|
| 19 |
Well, this is a solution to your needs. It allows using gettext |
|---|
| 20 |
tools for managing translations, yet it doesn't require gettext |
|---|
| 21 |
library at all. It parses generated MO files directly, and thus |
|---|
| 22 |
might be a bit slower than the (maybe provided) gettext library. |
|---|
| 23 |
|
|---|
| 24 |
PHP-gettext is a simple reader for GNU gettext MO files. Those |
|---|
| 25 |
are binary containers for translations, produced by GNU msgfmt. |
|---|
| 26 |
|
|---|
| 27 |
Why? |
|---|
| 28 |
|
|---|
| 29 |
I got used to having gettext work even without gettext |
|---|
| 30 |
library. It's there in my favourite language Python, so I was |
|---|
| 31 |
surprised that I couldn't find it in PHP. I even Googled for it, |
|---|
| 32 |
but to no avail. |
|---|
| 33 |
|
|---|
| 34 |
So, I said, what the heck, I'm going to write it for this |
|---|
| 35 |
disguisting language of PHP, because I'm often constrained to it. |
|---|
| 36 |
|
|---|
| 37 |
Features |
|---|
| 38 |
|
|---|
| 39 |
o Support for simple translations |
|---|
| 40 |
Just define a simple alias for translate() function (suggested |
|---|
| 41 |
use of _() or gettext(); see provided example). |
|---|
| 42 |
|
|---|
| 43 |
o Support for ngettext calls (plural forms, see a note under bugs) |
|---|
| 44 |
You may also use plural forms. Translations in MO files need to |
|---|
| 45 |
provide this, and they must also provide "plural-forms" header. |
|---|
| 46 |
Please see 'info gettext' for more details. |
|---|
| 47 |
|
|---|
| 48 |
o Support for reading straight files, or strings (!!!) |
|---|
| 49 |
Since I can imagine many different backends for reading in the MO |
|---|
| 50 |
file data, I used imaginary abstract class StreamReader to do all |
|---|
| 51 |
the input (check streams.php). For your convenience, I've already |
|---|
| 52 |
provided two classes for reading files: FileReader and |
|---|
| 53 |
StringReader (CachedFileReader is a combination of the two: it |
|---|
| 54 |
loads entire file contents into a string, and then works on that). |
|---|
| 55 |
See example below for usage. You can for instance use StringReader |
|---|
| 56 |
when you read in data from a database, or you can create your own |
|---|
| 57 |
derivative of StreamReader for anything you like. |
|---|
| 58 |
|
|---|
| 59 |
|
|---|
| 60 |
Bugs |
|---|
| 61 |
|
|---|
| 62 |
Plural-forms field in MO header (translation for empty string, |
|---|
| 63 |
i.e. "") is treated according to PHP syntactic rules (it's |
|---|
| 64 |
eval()ed). Since these should actually follow C syntax, there are |
|---|
| 65 |
some problems. |
|---|
| 66 |
|
|---|
| 67 |
For instance, I'm used to using this: |
|---|
| 68 |
Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : \ |
|---|
| 69 |
n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2; |
|---|
| 70 |
but it fails with PHP (it sets $plural=2 instead of 0 for $n==1). |
|---|
| 71 |
|
|---|
| 72 |
The fix is usually simple, but I'm lazy to go into the details of |
|---|
| 73 |
PHP operator precedence, and maybe try to fix it. In here, I had |
|---|
| 74 |
to put everything after the first ':' in parenthesis: |
|---|
| 75 |
Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : \ |
|---|
| 76 |
(n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); |
|---|
| 77 |
That works, and I'm satisfied. |
|---|
| 78 |
|
|---|
| 79 |
Besides this one, there are probably a bunch of other bugs, since |
|---|
| 80 |
I hate PHP (did I mention it already? no? strange), and don't |
|---|
| 81 |
know it very well. So, feel free to fix any of those and report |
|---|
| 82 |
them back to me at <danilo@kvota.net>. |
|---|
| 83 |
|
|---|
| 84 |
Usage |
|---|
| 85 |
|
|---|
| 86 |
Put files streams.php and gettext.php somewhere you can load them |
|---|
| 87 |
from, and require 'em in where you want to use them. |
|---|
| 88 |
|
|---|
| 89 |
Then, create one 'stream reader' (a class that provides functions |
|---|
| 90 |
like read(), seekto(), currentpos() and length()) which will |
|---|
| 91 |
provide data for the 'gettext_reader', with eg. |
|---|
| 92 |
$streamer = new FileStream('data.mo'); |
|---|
| 93 |
|
|---|
| 94 |
Then, use that as a parameter to gettext_reader constructor: |
|---|
| 95 |
$wohoo = new gettext_reader($streamer); |
|---|
| 96 |
|
|---|
| 97 |
If you want to disable pre-loading of entire message catalog in |
|---|
| 98 |
memory (if, for example, you have a multi-thousand message catalog |
|---|
| 99 |
which you'll use only occasionally), use "false" for second |
|---|
| 100 |
parameter to gettext_reader constructor: |
|---|
| 101 |
$wohoo = new gettext_reader($streamer, false); |
|---|
| 102 |
|
|---|
| 103 |
From now on, you have all the benefits of gettext data at your |
|---|
| 104 |
disposal, so may run: |
|---|
| 105 |
print $wohoo->translate("This is a test"); |
|---|
| 106 |
print $wohoo->ngettext("%d bird", "%d birds", $birds); |
|---|
| 107 |
|
|---|
| 108 |
You might need to pass parameter "-k" to xgettext to make it |
|---|
| 109 |
extract all the strings. In above example, try with |
|---|
| 110 |
xgettext -ktranslate -kngettext:1,2 file.php |
|---|
| 111 |
what should create messages.po which contains two messages for |
|---|
| 112 |
translation. |
|---|
| 113 |
|
|---|
| 114 |
I suggest creating simple aliases for these functions (see |
|---|
| 115 |
example/pigs.php for how do I do it, which means it's probably a |
|---|
| 116 |
bad way). |
|---|
| 117 |
|
|---|
| 118 |
|
|---|
| 119 |
Usage with gettext.inc (standard gettext interfaces emulation) |
|---|
| 120 |
|
|---|
| 121 |
Check example in examples/pig_dropin.php, basically you include |
|---|
| 122 |
gettext.inc and use all the standard gettext interfaces as |
|---|
| 123 |
documented on: |
|---|
| 124 |
|
|---|
| 125 |
http://www.php.net/gettext |
|---|
| 126 |
|
|---|
| 127 |
The only catch is that you can check return value of setlocale() |
|---|
| 128 |
to see if your locale is system supported or not. |
|---|
| 129 |
|
|---|
| 130 |
|
|---|
| 131 |
Example |
|---|
| 132 |
|
|---|
| 133 |
See in examples/ subdirectory. There are a couple of files. |
|---|
| 134 |
pigs.php is an example, serbian.po is a translation to Serbian |
|---|
| 135 |
language, and serbian.mo is generated with |
|---|
| 136 |
msgfmt -o serbian.mo serbian.po |
|---|
| 137 |
There is also simple "update" script that can be used to generate |
|---|
| 138 |
POT file and to update the translation using msgmerge. |
|---|
| 139 |
|
|---|
| 140 |
Interesting TODO: |
|---|
| 141 |
|
|---|
| 142 |
o Try to parse "plural-forms" header field, and to follow C syntax |
|---|
| 143 |
rules. This won't be easy. |
|---|
| 144 |
|
|---|
| 145 |
Boring TODO: |
|---|
| 146 |
|
|---|
| 147 |
o Learn PHP and fix bugs, slowness and other stuff resulting from |
|---|
| 148 |
my lack of knowledge (but *maybe*, it's not my knowledge that is |
|---|
| 149 |
bad, but PHP itself ;-). |
|---|
| 150 |
|
|---|
| 151 |
(This is mostly done thanks to Nico Kaiser.) |
|---|
| 152 |
|
|---|
| 153 |
o Try to use hash tables in MO files: with pre-loading, would it |
|---|
| 154 |
be useful at all? |
|---|
| 155 |
|
|---|
| 156 |
Never-asked-questions: |
|---|
| 157 |
|
|---|
| 158 |
o Why did you mark this as version 1.0 when this is the first code |
|---|
| 159 |
release? |
|---|
| 160 |
|
|---|
| 161 |
Well, it's quite simple. I consider that the first released thing |
|---|
| 162 |
should be labeled "version 1" (first, right?). Zero is there to |
|---|
| 163 |
indicate that there's zero improvement and/or change compared to |
|---|
| 164 |
"version 1". |
|---|
| 165 |
|
|---|
| 166 |
I plan to use version numbers 1.0.* for small bugfixes, and to |
|---|
| 167 |
release 1.1 as "first stable release of version 1". |
|---|
| 168 |
|
|---|
| 169 |
This may trick someone that this is actually useful software, but |
|---|
| 170 |
as with any other free software, I take NO RESPONSIBILITY for |
|---|
| 171 |
creating such a masterpiece that will smoke crack, trash your |
|---|
| 172 |
hard disk, and make lasers in your CD device dance to the tune of |
|---|
| 173 |
Mozart's 40th Symphony (there is one like that, right?). |
|---|
| 174 |
|
|---|
| 175 |
o Can I...? |
|---|
| 176 |
|
|---|
| 177 |
Yes, you can. This is free software (as in freedom, free speech), |
|---|
| 178 |
and you might do whatever you wish with it, provided you do not |
|---|
| 179 |
limit freedom of others (GPL). |
|---|
| 180 |
|
|---|
| 181 |
I'm considering licensing this under LGPL, but I *do* want |
|---|
| 182 |
*every* PHP-gettext user to contribute and respect ideas of free |
|---|
| 183 |
software, so don't count on it happening anytime soon. |
|---|
| 184 |
|
|---|
| 185 |
I'm sorry that I'm taking away your freedom of taking others' |
|---|
| 186 |
freedom away, but I believe that's neglible as compared to what |
|---|
| 187 |
freedoms you could take away. ;-) |
|---|
| 188 |
|
|---|
| 189 |
Uhm, whatever. |
|---|