omega-mvc/gettext
Composer 安装命令:
composer require omega-mvc/gettext
包简介
PHP gettext manager
README 文档
README
Gettext is a PHP 8.4+ library for working with internationalization (i18n) and localization (l10n).
It provides tools to import, export, edit, and merge translations across multiple formats, such as PO, MO, PHP arrays, JSON, and JavaScript files.
The library is designed to be flexible: you can scan source code to extract translatable strings, load existing translations from files, manipulate them in PHP, and finally generate the compiled output in the format required by your application or framework.
Whether you are building a multilingual website, a PHP application, or a project that integrates templates and scripts, this package aims to provide a consistent API for all gettext-related operations.
Table of Contents
Installation
composer require omegamvc/gettext
Running Test
vendor/bin/phpunit
Classes and functions
This package contains the following classes:
Gettext\Translation- A translation definitionGettext\Translations- A collection of translations (under the same domain)Gettext\Translator-Gettext\Scanner\*- Scan files to extract translations (php, js, twig templates, ...)Gettext\Loader\*- Load translations from different formats (po, mo, json, ...)Gettext\Generator\*- Export translations to various formats (po, mo, json, ...)
Quick Start
use Gettext\Loader\PoLoader; use Gettext\Generator\MoGenerator; //import from a .po file: $loader = new PoLoader(); $translations = $loader->loadFile('locales/gl.po'); //edit some translations: $translation = $translations->find(null, 'apple'); if ($translation) { $translation->translate('Mazá'); } //export to a .mo file: $generator = new MoGenerator(); $generator->generateFile($translations, 'Locale/gl/LC_MESSAGES/messages.mo');
Translation
The Gettext\Translation class stores all information about a translation: the original text, the translated text, source references, comments, etc.
use Gettext\Translation; $translation = Translation::create('comments', 'One comment', '%s comments'); $translation->translate('Un comentario'); $translation->translatePlural('%s comentarios'); $translation->getReferences()->add('templates/comments/comment.php', 34); $translation->getComments()->add('To display the amount of comments in a post'); echo $translation->getContext(); // comments echo $translation->getOriginal(); // One comment echo $translation->getTranslation(); // Un comentario // etc...
Translations
The Gettext\Translations class stores a collection of translations:
use Gettext\Translations; $translations = Translations::create('my-domain'); //You can add new translations: $translation = Translation::create('comments', 'One comment', '%s comments'); $translations->add($translation); //Find a specific translation $translation = $translations->find('comments', 'One comment'); //Edit headers, domain, etc $translations->getHeaders()->set('Last-Translator', 'Oscar Otero'); $translations->setDomain('my-blog');
Translator
use Gettext\Translator; //Create a new instance of the translator $t = new Translator(); //Load the translations from php files (generated by Gettext\Extractors\PhpArray) $t->loadTranslations( 'locales/gl/domain1.php', 'locales/gl/domain2.php', 'locales/gl/domain3.php', ); //Now you can use it in your templates echo $t->gettext('apple');
GettextTranslator
The class Gettext\GettextTranslator uses the gettext extension. It's useful because combines the performance of using real gettext functions but with the same API as Translator class, so you can switch to one or other translator without change code of your app.
use Gettext\GettextTranslator; //Create a new instance $t = new GettextTranslator(); //It detects the environment variables to set the locale, but you can change it: $t->setLanguage('gl'); //Load the domains: $t->loadDomain('messages', 'project/Locale'); //this means you have the file "project/Locale/gl/LC_MESSAGES/messages.mo" //Now you can use it in your templates echo $t->gettext('apple');
Translator functions
To ease the use of translations in your php templates, you can use the provided functions:
use Gettext\TranslatorFunctions; //Register the translator to use the global functions TranslatorFunctions::register($t); echo __('apple'); // it's the same as $t->gettext('apple');
You can scan the php files containing these functions and extract the values with the PhpCode extractor:
<!-- index.php --> <html lang="en"> <body> <?= __('Hello world'); ?> </body> </html>
Loaders
This package includes the following loaders:
ArrayLoaderJsonLoaderMoLoaderPoLoaderStrictPoLoader
The loaders allow to get gettext values from multiple formats. For example, to load a .po file:
ArrayLoader
JsonLoader
MoLoader
PoLoader
use Gettext\Loader\PoLoader; $loader = new PoLoader(); //From a file $translations = $loader->loadFile('locales/en.po'); //From a string $string = file_get_contents('locales2/en.po'); $translations = $loader->loadString($string);
StrictPoLoader
StrictPoLoader is a parser more aligned to the GNU gettext tooling with the same expectations and failures (see the tests for more details).
- It will fail with an exception when there's anything wrong with the syntax, and display the reason together with the line/byte where it happened.
- It might also emit useful warnings, e.g. when there are more/less plural translations than needed, missing translation header, dangling comments not associated with any translation, etc.
- Due to its strictness and speed (about 50% slower than the
PoLoader), it might be interesting to be used as a kind of.polinter in a build system. - It also implements the previous translation comment (e.g.
#| msgid "previous") and extra escapes (16-bit unicode\u, 32-bit unicode\U, hexadecimal\xFFand octal\77).
The usage is basically the same as the PoLoader:
use Gettext\Loader\StrictPoLoader; $loader = new StrictPoLoader(); //From a file $translations = $loader->loadFile('locales/en.po'); //From a string $string = file_get_contents('locales2/en.po'); $translations = $loader->loadString($string); //Display error messages using "at line X column Y" instead of "at byte X" $loader->displayErrorLine = true; //Throw an exception when a warning happens $loader->throwOnWarning = true; //Retrieve the warnings $loader->getWarnings();
Generators
The generators export a Gettext\Translations instance to any format (po, mo, etc.).
This package includes the following generators:
ArrayGeneratorJsonGeneratorMoGeneratorPoGenerator
ArrayGenerator
ArrayGenerator generates PHP array files from gettext translations with optional pretty-printing and strict types.
use Gettext\Generator\ArrayGenerator; use Gettext\Loader\PoLoader; // Load translations from a .po file $loader = new PoLoader(); $translations = $loader->loadFile('locales/en.po'); // Generate PHP array file with pretty print and strict types $generator = new ArrayGenerator([ 'pretty' => true, 'strictTypes' => true ]); $phpCode = $generator->generateString($translations); file_put_contents('locale/en/messages.php', $phpCode);
JsonGenerator
JsonGenerator generates JSON files from gettext translations with configurable JSON options.
use Gettext\Loader\PoLoader; use Gettext\Loader\JsonLoader; use Gettext\Generator\JsonGenerator; use Gettext\Translations; //Load a .po file and export to .json $translations = (new PoLoader())->loadFile('locales/translations.po'); (new JsonGenerator())->generateFile($translations, 'locales/translations.json'); //You can load the json file with JsonLoader $loadedTranslations = (new JsonLoader())->loadFile('locales/translations.json');
MoGenerator
MoGenerator generates binary .mo files from gettext translations, optionally including headers
use Gettext\Loader\PoLoader; use Gettext\Generator\MoGenerator; //Load a PO file $poLoader = new PoLoader(); $translations = $poLoader->loadFile('locales/en.po'); //Save to MO file $moGenerator = new MoGenerator(); $moGenerator->generateFile($translations, 'locales/en.mo'); //Or return as a string $content = $moGenerator->generateString($translations); file_put_contents('locales/en.mo', $content);
PoGenerator
PoGenerator generates human-readable .po files from gettext translations.
use Gettext\Loader\PoLoader; use Gettext\Generator\PoGenerator; // Load translations from a PO file $loader = new PoLoader(); $translations = $loader->loadFile('locales/en.po'); // Generate a .po string $generator = new PoGenerator(); $poContent = $generator->generateString($translations); // Save to a file file_put_contents('locales/generated.po', $poContent);
Scanners
Scanners allow to search and extract new gettext entries from different sources like php files, twig templates, blade templates, etc. Unlike loaders, scanners allows to extract gettext entries with different domains at the same time:
This package includes the following scanners:
JsScannerPhpScanner
JsScanner
use Gettext\Scanner\JsScanner; use Gettext\Generator\PoGenerator; use Gettext\Translations; //Create a new scanner, adding a translation for each domain we want to get: $jsScanner = new JsScanner( Translations::create('domain1'), Translations::create('domain2'), Translations::create('domain3') ); //Scan files foreach (glob('*.js') as $file) { $jsScanner->scanFile($file); } //Save the translations in .po files $generator = new PoGenerator(); foreach ($jsScanner->getTranslations() as $translations) { $domain = $translations->getDomain(); $generator->generateFile($translations, "locales/{$domain}.po"); }
PhpScanner
use Gettext\Scanner\PhpScanner; use Gettext\Generator\PoGenerator; use Gettext\Translations; //Create a new scanner, adding a translation for each domain we want to get: $phpScanner = new PhpScanner( Translations::create('domain1'), Translations::create('domain2'), Translations::create('domain3') ); //Set a default domain, so any translations with no domain specified, will be added to that domain $phpScanner->setDefaultDomain('domain1'); //Extract all comments starting with 'i18n:' and 'Translators:' $phpScanner->extractCommentsStartingWith('i18n:', 'Translators:'); //Scan files foreach (glob('*.php') as $file) { $phpScanner->scanFile($file); } //Save the translations in .po files $generator = new PoGenerator(); foreach ($phpScanner->getTranslations() as $domain => $translations) { $generator->generateFile($translations, "locales/{$domain}.po"); }
Merging translations
You will want to update or merge translations. The function mergeWith create a new Translations instance with other translations merged:
$translations3 = $translations1->mergeWith($translations2);
But sometimes this is not enough, and this is why we have merging options, allowing to configure how two translations will be merged. These options are defined as constants in the Gettext\Merge class, and are the following:
| Constant | Description |
|---|---|
Merge::TRANSLATIONS_OURS |
Use only the translations present in $translations1 |
Merge::TRANSLATIONS_THEIRS |
Use only the translations present in $translations2 |
Merge::TRANSLATIONS_OVERRIDE |
Override the translation and plural translations with the value of $translation2 |
Merge::HEADERS_OURS |
Use only the headers of $translations1 |
Merge::HEADERS_REMOVE |
Use only the headers of $translations2 |
Merge::HEADERS_OVERRIDE |
Overrides the headers with the values of $translations2 |
Merge::COMMENTS_OURS |
Use only the comments of $translation1 |
Merge::COMMENTS_THEIRS |
Use only the comments of $translation2 |
Merge::EXTRACTED_COMMENTS_OURS |
Use only the extracted comments of $translation1 |
Merge::EXTRACTED_COMMENTS_THEIRS |
Use only the extracted comments of $translation2 |
Merge::FLAGS_OURS |
Use only the flags of $translation1 |
Merge::FLAGS_THEIRS |
Use only the flags of $translation2 |
Merge::REFERENCES_OURS |
Use only the references of $translation1 |
Merge::REFERENCES_THEIRS |
Use only the references of $translation2 |
Use the second argument to configure the merging strategy:
$strategy = Merge::TRANSLATIONS_OURS | Merge::HEADERS_OURS; $translations3 = $translations1->mergeWith($translations2, $strategy);
There are some typical scenarios, one of the most common:
- Scan php templates searching for entries to translate
- Complete these entries with the translations stored in a .po file
- You may want to add new entries to the .po file
- And also remove those entries present in the .po file but not in the templates (because they were removed)
- But you want to update some translations with new references and extracted comments
- And keep the translations, comments and flags defined in .po file
For this scenario, you can use the option Merge::SCAN_AND_LOAD with the combination of options to fit this needs (SCAN new entries and LOAD a .po file).
$newEntries = $scanner->scanFile('template.php'); $previousEntries = $loader->loadFile('translations.po'); $updatedEntries = $newEntries->mergeWith($previousEntries);
gettext language list automatically generated from CLDR data
Static usage
To use the languages data generated from this tool you can use the bin/export-plural-rules command.
Export command line options
export-plural-rules supports the following options:
--us-asciiIf specified, the output will contain only US-ASCII characters. If not specified, the output charset is UTF-8.--languages=<LanguageId>[,<LanguageId>,...]]--language=<LanguageId>[,<LanguageId>,...]]Export only the specified language codes. Separate languages with commas; you can also use this argument more than once; it's case-insensitive and accepts both '_' and '-' as locale chunks separator (e.g. we acceptit_ITas well asit-it). If this option is not specified, the result will contain all the available languages.--reduce=yes|noIf set to yes the output won't contain languages with the same base language and rules. For instancenl_BE(Flemish) will be omitted because it's the same asnl(Dutch). Defaults tonoif--languagesis specified, toyesotherwise.--parenthesis=yes|noIf set to no, extra parenthesis will be omitted in generated plural rules formulas. Those extra parenthesis are needed to create a PHP-compatible formula. Defaults toyes--output=<file name>If specified, the output will be saved to<file name>. If not specified we'll output to standard output.
Export formats
export-plural-rules can generate data in the following formats:
-
json: compressed JSON dataexport-plural-rules json
-
prettyjson: uncompressed JSON dataexport-plural-rules prettyjson
-
html: html table (see the result)export-plural-rules html
-
php: build a php file that can be includedexport-plural-rules --output=yourfile.php php
Then you can use that generated file in your php scripts:
$languages = include 'yourfile.php';
-
ruby: build a ruby file that can be includedexport-plural-rules --parenthesis=no --output=yourfile.rb ruby
Then you can use that generated file in your ruby scripts:
require './yourfile.rb' PLURAL_RULES['en']
-
xml: generate an XML document (here you can find the xsd XML schema)export-plural-rules xml
-
po: generate the gettext .po headers for a single languageexport-plural-rules po --language=YourLanguageCode
Dynamic usage
With Composer
You can use Composer to include this tool in your project.
Simply launch composer require gettext/languages or add "gettext/languages": "*" to the "require" section of your composer.json file.
Without Composer
If you don't use composer in your project, you can download this package in a directory of your project and include the autoloader file:
require_once 'path/to/src/autoloader.php';
Main methods
The most useful functions of these tools are the following
$allLanguages = Gettext\Languages\Language::getAll(); ... $oneLanguage = Gettext\Languages\Language::getById('en_US'); ...
getAll returns a list of Gettext\Languages\Language instances, getById returns a single Gettext\Languages\Language instance (or null if the specified language identifier is not valid).
The main properties of the Gettext\Languages\Language instances are:
id: the normalized language ID (for instanceen_US)name: the language name (for instanceAmerican Englishforen_US)supersededBy: the code of a language that supersedes this language code (for instance,jwis superseded byjvto represent the Javanese language)script: the script name (for instance, forzh_Hans-Simplified Chinese- the script isSimplified Han)territory: the name of the territory (for instanceUnited Statesforen_US)baseLanguage: the name of the base language (for instanceEnglishforen_US)formula: the gettext formula to distinguish between different plural rules. For instancen != 1categories: the plural cases applicable for this language. It's an array ofGettext\Languages\Categoryinstances. Each instance has these properties:id: can be (in this order) one ofzero,one,two,few,manyorother. Theothercase is always present.examples: a representation of some values for which this plural case is valid (examples are simple numbers like1or complex ranges like0, 2~16, 100, 1000, 10000, 100000, 1000000, …)
Is this data correct?
Yes - as far as you trust the Unicode CLDR project.
The conversion from CLDR to gettext includes also a lot of tests to check the results. And all passes 😉.
Reference
CLDR
The CLDR specifications define the following variables to be used in the CLDR plural formulas:
n: absolute value of the source number (integer and decimals) (eg:9.870=>9.87)i: integer digits of n (eg:9.870=>9)v: number of visible fraction digits in n, with trailing zeros (eg:9.870=>3)w: number of visible fraction digits in n, without trailing zeros (eg:9.870=>2)f: visible fractional digits in n, with trailing zeros (eg:9.870=>870)t: visible fractional digits in n, without trailing zeros (eg:9.870=>87)c: exponent of the power of 10 used in compact decimal formatting (eg:98c7=>7)e: synonym forc
gettext
The gettext specifications define the following variables to be used in the gettext plural formulas:
n: unsigned long int
Conversion CLDR > gettext
| CLDR variable | gettext equivalent |
|---|---|
n |
n |
i |
n |
v |
0 |
w |
0 |
f |
empty |
t |
empty |
c |
empty |
e |
empty |
Parenthesis in ternary operators
The generated gettext formulas contain some extra parenthesis, in order to avoid problems in some programming language.
For instance, let's assume we have this formula:
(0 == 0) ? 0 : (0 == 1) ? 1 : 2
- in C it evaluates to
0since is the same as(0 == 0) ? 0 : ((0 == 1) ? 1 : 2) - in Java, it evaluates to
0since is the same as(0 == 0) ? 0 : ((0 == 1) ? 1 : 2) - in JavaScript, it evaluates to
0since is the same as(0 == 0) ? 0 : ((0 == 1) ? 1 : 2) - in PHP, it evaluates to
2since is the same as((0 == 0) ? 0 : (0 == 1)) ? 1 : 2
So, in order to avoid problems, instead of a simple
a ? 0 : b ? 1 : 2
the resulting formulas will be in this format:
a ? 0 : (b ? 1 : 2)
统计信息
- 总下载量: 0
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 1
- 依赖项目数: 1
- 推荐数: 0
其他信息
- 授权协议: GPL-3.0-or-later
- 更新时间: 2026-07-12