PHP library to collect and manipulate gettext (.po, .mo, .php, .json, etc)

Last update: Jul 30, 2022

Gettext

Latest Version on Packagist Software License ico-ga Total Downloads

Note: this is the documentation of the new 5.x version. Go to 4.x branch if you're looking for the old 4.x version

Created by Oscar Otero http://oscarotero.com [email protected] (MIT License)

Gettext is a PHP (^7.2) library to import/export/edit gettext from PO, MO, PHP, JS files, etc.

Installation

composer require gettext/gettext

Classes and functions

This package contains the following classes:

  • Gettext\Translation - A translation definition
  • Gettext\Translations - A collection of translations (under the same domain)
  • 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, ...)

Usage example

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');

Loaders

The loaders allows to get gettext values from any format. For example, to load a .po file:

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);

This package includes the following loaders:

  • MoLoader
  • PoLoader

And you can install other formats with loaders and generators:

Generators

The generators export a Gettext\Translations instance to any format (po, mo, etc).

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);

This package includes the following generators:

  • MoGenerator
  • PoGenerator

And you can install other formats with loaders and generators:

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:

use Gettext\Scanner\PhpScanner;
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);
}

//Get the translations
list('domain1' => $domain1, 'domain2' => $domain2, 'domain3' => $domain3) = $phpScanner->getTranslations();

This package does not include any scanner by default. But there are some that you can install:

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::TRANSLATION_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);

More common scenarios may be added in a future.

Contributors

Thanks to all contributors specially to @mlocati.


Please see CHANGELOG for more information about recent changes and CONTRIBUTING for contributing details.

The MIT License (MIT). Please see LICENSE for more information.

GitHub

https://github.com/oscarotero/Gettext
Comments
  • 1. Add test for some PHP special chars

    Let's consider this source code:

    <div>
        <p><?php __('plain'); ?></p>
        <p><?php __('DATE \a\t TIME'); ?></p>
        <p><?php __("DATE \a\\t TIME"); ?></p>
        <p><?php __("DATE \\a\\t TIME"); ?></p>
        <p><?php __("FIELD\tFIELD"); ?></p>
    </div>
    

    Here we have only 3 different strings, but the php extractor finds these strings:

    msgid "plain"
    msgstr ""
    
    msgid "DATE \\a\\t TIME"
    msgstr ""
    
    msgid "DATE \\a\\\\t TIME"
    msgstr ""
    
    msgid "DATE \\\\a\\\\t TIME"
    msgstr ""
    
    msgid "FIELD\\tFIELD"
    msgstr ""
    
    Reviewed by mlocati at 2016-02-16 11:19
  • 2. Native (GettextTranslator) translator does not work with php 5.5

    Hi guys.

    I have one problem with your project and php ver. 5.5. When i used 5.3 or 5.4 - all works fine, but when i updated php to 5.5 - native translator (class GettextTranslator, vendor\gettext\gettext\src\GettextTranslator.php) stop working for me. If i use php Translator (vendor\gettext\gettext\src\Translator.php) - all is ok again, but i need exactly in native translator.

    Test string:

    Original string: Контакты
    Translated string (en): Contacts
    Translated string (de): Kontakte
    

    Files structure:

    index.php
    data
      \- locales
         \- de
            \- LC_MESSAGES
               |- messages.mo
               \- messages.po
         \- en
            \- LC_MESSAGES
               |- messages.mo
               \- messages.po
    

    Test code:

    $translator = new \Gettext\GettextTranslator();
    $translator->setLanguage('de')->loadDomain(
      'messages',
      'data/locales'
    );
    die($translator->gettext('Контакты'));
    

    First test:

    $ php -v
    PHP 5.4.45 (cli) (built: Sep  2 2015 23:48:30)
    Copyright (c) 1997-2014 The PHP Group
    Zend Engine v2.4.0, Copyright (c) 1998-2014 Zend Technologies
    

    Output:

    Kontakte
    

    All is ok. Second test:

    $ php -v
    PHP 5.5.33 (cli) (built: Mar  2 2016 15:19:21)
    Copyright (c) 1997-2015 The PHP Group
    Zend Engine v2.5.0, Copyright (c) 1998-2015 Zend Technologies
    

    Output:

    Контакты
    

    My string is not translated. Test code was not changed.

    Gettext installed by composer and source has no any changes by me:

    {
      "require": {
        "php": ">=5.5",
        "gettext/gettext": "3.*@dev",
      },
    }
    

    Server software:

    • Apache 2.4
    • PHP 5.3, 5.4, 5.5
    • Windows

    Can you fix this or say me how can i do it?

    Reviewed by tarampampam at 2016-04-27 10:03
  • 3. WIP: Twig AST extractor for Timber

    Current Twig parser operates by

    1. Transforming Twig as PHP code = tokenize() + parse() + render()
    2. Using a PhpCode extractor (and its specific configuration about function name)

    The disadvantages are:

    • Twig rendered PHP code can be transformed/wrapped in call_user_func() making that gettext functions undetectable by PhpCode extractor. (See for example timber/timber#1753).
    • Can't handle templates making use of custom Twig extensions (very common)

    This patchset implements an extractor that:

    • Parses Twig generated AST tree (= tokenize()+parse())
    • Recursively iterates over node three to find function gettext calls.

    Advantages:

    • Operating sooner, at the AST level, Twig expressions like {{ __("foo", "domain") }} aren't yet wrapped.
    • More robust because it directly iterates over the AST from Twig parser instead of looking at PHP source-code.
    • Supports initialized $twig environment, thus supporting user-defined extensions?
    • Possibly more efficient.

    ToDos:

    1. $options['twig'] being a static variable, we can NOT initialize it in a row with different parameters. Would get a LogicException: Unable to add function "action" as extensions have already been initialized. Solution: don't use static variables for this.

    2. Translation/Notes comments are not yet supported.

    3. Timber needs a tweak to avoid a fatal error: Add, to lib/Timber.php

      function add_action() { }
      function apply_filters() {}
    

    The issue is that when loaded through composer Timber constructor forcefully calls init() which make use of WP functions.

    Reviewed by drzraf at 2018-10-28 21:55
  • 4. Empty translation prepends generated PO

    When generating PO files, this line always prepends an empty translation:

    msgid ""
    msgstr ""
    "Project-Id-Version: \n"
    "Report-Msgid-Bugs-To: \n"
    ...
    

    Because of this, if I have an empty text in my gettext or __ function, e.g. __(''), it will be replaced by Project-Id-Version: Report-Msgid-Bugs-To: ... and the entire header.

    What is the purpose of this empty translation? Can it be removed?

    Reviewed by marcandrews at 2016-05-06 14:57
  • 5. Extraction of variables and concatenated strings

    I run a few tests again a PHP project with ~4300 translatable strings, comparing the results of xgettext and those of the PhpCode extractor.

    I only found two problems:

    1. xgettext (correctly) does not extracts strings like the me in __($avoid['me']). I think that the correct solution would be to do not extract strings with variables (since it does not have any sense), but gettext stops as soon as it find a variable (for instance from __('Stop at the variable'.$var.'!') it extracts Stop at the variable) - so I'd adopt this same approach for consistency.

    2. Consider this example:

    __(
      'line1'
     .' line2')
    

    xgettext (correctly) extracts 'line1 line2', but the PhpCode extractor only extracts 'line1'.

    I added some tests here to highlight the above problems.

    Reviewed by mlocati at 2016-02-18 10:11
  • 6. Plural rules definition

    @oscarotero Could you wait a few days before publishing a new release? IMHO some of the plural rules that we took from http://localization-guide.readthedocs.org/en/latest/l10n/pluralforms.html are not correct. I discovered two discrepancies but it seems that nobody is taking care of them, so IMHO that repository is not well maintained. I'm going to write a script that takes the rules from CLDR and converts them to gettext. It's not that easy, since CLDR rules refer to decimals and negative numbers, whereas gettext works with unsigned integers, but I'm working on it.

    Reviewed by mlocati at 2015-02-10 07:25
  • 7. Faster extraction

    When adding string to the Translations instance, we call Translations::offsetSet that in turns calls Translation:is many times. If we load 5.000 strings, its inner cycle calls Translation:is 12.497.500 times (5000*(5000-1)/2). What about adding an option to skip this check?

    With the code of this pull request, the checks are skipped by default for the .po and .mo importers (but it's configurable by writing Gettext\Extractors\Mo::$skipIntegrityChecksOnImport.

    Reviewed by mlocati at 2015-01-27 09:19
  • 8. Add support for XLIFF unit IDs

    This is the "least-invasive" way to add unit IDs to the existing toolset, without potentially introducing problems with uniqueness (XLIFF doesn't require uniqueness of IDs in any way).

    Reviewed by asmecher at 2019-08-20 23:19
  • 9. Message uniqueness

    Let's say we have a test.po file like this:

    msgid ""
    msgstr ""
    "Project-Id-Version: \n"
    "POT-Creation-Date: \n"
    "PO-Revision-Date: \n"
    "Language: it\n"
    "Language-Team: \n"
    "Last-Translator: \n"
    "Plural-Forms: nplurals=2; plural=(n != 1);\n"
    "MIME-Version: 1.0\n"
    "Content-Type: text/plain; charset=UTF-8\n"
    "Content-Transfer-Encoding: 8bit\n"
    
    msgid "1 child"
    msgstr ""
    
    msgid "1 child"
    msgid_plural "2 children"
    msgstr[0] ""
    msgstr[1] ""
    

    If we compile it with msgfmt we have:

    test.po:17: duplicate message definition...
    test.po:15: ...this is the location of the first definition
    msgfmt: found 1 fatal error
    

    So, we may not have in .po files two strings with same context+msgid but different plurals.

    I think that we should apply two changes:

    1. Revert https://github.com/oscarotero/Gettext/pull/46
    2. Add an alternative method to Translations::find that does not take in account the plural form
    Reviewed by mlocati at 2015-01-20 07:17
  • 10. I need help configuring a gettext project

    Hi! I trying to create a sample project in PHP with gettext integration for localization, but I can't manage to create the right architecture in order to use it.

    I tried the different examples in the README file but I don't see how to link everything together. Is there a complete and concrete example somewhere? I searched the Web and everything I found is kind of outdated.

    I only want to have the possibility to generate po files from php files and use them for localization changing the language from a variable.

    For example if I have a index.php file:

    <?php
    
    echo _("Hello world!")
    

    How should I use php-gettext to extract this string into a po file in a structured locale folder and use it back to translate the page according to the specified language?

    Thanks in advance for your help :smile: I'm kind of lost.

    Reviewed by NathanBnm at 2020-02-23 14:28
  • 11. Add support for translation extraction from VueJs templates

    How it works - VueJs tempaltes basically are valid HTML. We extract template part (then extract dynamic attributes which are JS expressions and extract expressions from within template elements) and script part and parse them both as a regular JS.

    Reviewed by briedis at 2018-04-29 13:09
  • 12. translations findById

    Hello,

    First of all thanks for the work on this library, highly appreciated :)

    Second, I would like to be able to find translations by their ids. Currently it is only possible to search by translations. https://github.com/php-gettext/Gettext/blob/master/src/Translations.php#L169

    May I propose a PR to implement the changes? It should be fairly easy to implement.

    Cheers,

    Reviewed by ouhman at 2022-02-22 13:19
  • 13. Make use of msgctxt

    It is possible to make use of msgctxt context into the .po file definition? There is no clear method from native functions to get the translation of a context either. Or I haven't found.

    The method Translattion::withContext ($s) { .. } / Translation:getContext ($s) { .. } would meet this?

    Thanks,

    Reviewed by limogin at 2022-02-10 16:02
  • 14. Gettext doesn`t extract template literals from JS

    Hi,

    in version 4.6.1 library work fine with extracting translations from template literals in JS files, but after upgrade to 4.8 library doesnt extract translations from template literals for example new version doesnt extract "Foo" from string Text ${__("Foo")} bar but older version does.

    Unfortunately I didn`t found solution or some clues to resolve this problem...

    Thanks for response.

    Reviewed by DaweTaller at 2021-11-01 14:42
  • 15. How to load multiple *.mo files (general and for specific textdomain)

    First of all, thank you for a library. Second, I don't know how to load multiple *.mo translation files and use them with textdomain context.

    This is my code so far:

    use Gettext\Translator;
    use Gettext\Loader\MoLoader;
    
    $lang = 'sk' // set by a side function
    define('ROOT', '/path/to/root');
    
    function __( string $string, string $textdomain = null ) {
    
        static $t = null;
    
        global $basic_lang;
    
        $translation = ROOT . '/languages/' . $lang . '.mo';
    
        if (!file_exists( $translation )) {
            return $string;
        }
    
        if ($t === null) {
    
            $loader = new MoLoader();
            $t = Translator::createFromTranslations( $loader->loadFile( $translation ) );
        }
    
        //return $textdomain ? $t->dgettext( $textdomain, $string ) : $t->gettext( $string );
        return $t->gettext( $string );
    
    }
    
    //echo __( 'Buy a ticket', 'reservation' ); // does not work right now, how to make it work?
    echo __( 'Buy a ticket' ); // works
    

    I guess that mode code samples would be helpful for others too.

    Reviewed by jasomdotnet at 2021-07-13 16:09
  • 16. Why PHP >= 7.2 for Gettext v5?

    As per https://github.com/wp-cli/i18n-command/pull/217, WP-Cli i18n-command still use v4 (which does not provide Twig scanner, among others) because it stick to package supporting PHP 5.6. What is itself a consequence of wp-cli requirements regarding WP core which tend to support old version of PHP for a long time.

    I wonder why exactly, in October 2019, f6ca83d7d7c6 bumped the mimimum requirements to PHP 7.2? Isn't the codebase/testsuite compatible with PHP 5.6 at all? And if it is, is lowering the min version back to 5.6 feasible?

    Reviewed by drzraf at 2021-07-09 19:41
Check if a translated value in a JSON column is unique in the database.

Laravel Unique Translation IMPORTANT: March 2022 It's horrible to see what is happening now in Ukraine, as Russian army is bombarding houses, hospital

Jul 30, 2022
Brings localization feature to tightenco/jigsaw using JSON files

Jigsaw localization Brings localization feature to tightenco/jigsaw using JSON files. Get started Setup Bring jigsaw-localization to your Jigsaw proje

Jul 3, 2022
Geographer is a PHP library that knows how any country, state or city is called in any language
Geographer is a PHP library that knows how any country, state or city is called in any language

Geographer Geographer is a PHP library that knows how any country, state or city is called in any language. Documentation on the official website Incl

Jul 26, 2022
Official PHP library for the DeepL language translation API.

deepl-php Official PHP client library for the DeepL API. The DeepL API is a language translation API that allows other computer programs to send texts

Jul 20, 2022
A PHP internationalization library, powered by CLDR data.

intl A PHP 7.1+ internationalization library, powered by CLDR data. Features: NumberFormatter and CurrencyFormatter, inspired by intl. Currencies Lang

Jul 6, 2022
🗓 A library to help you work with dates in multiple languages, based on Carbon.

Date This date library extends Carbon with multi-language support. Methods such as format, diffForHumans, parse, createFromFormat and the new timespan

Jul 21, 2022
[virion] Language management library for automatic translation
[virion] Language management library for automatic translation

libtranslator :: library for automatic translation ✔️ Multilingual support for plugin messages ✔️ Translation language is set according to the player

Jul 5, 2022
Patchwork UTF-8 for PHP: Extensive, portable and performant handling of UTF-8 and grapheme clusters for PHP

Patchwork UTF-8 for PHP Patchwork UTF-8 gives PHP developpers extensive, portable and performant handling of UTF-8 and grapheme clusters. It provides

Jul 21, 2022
FBT - a internationalization framework for PHP designed to be not just powerful and flexible, but also simple and intuitive
FBT - a internationalization framework for PHP designed to be not just powerful and flexible, but also simple and intuitive

FBT is an internationalization framework for PHP designed to be not just powerful and flexible, but also simple and intuitive. It helps with the follo

Apr 24, 2022
List of 77 languages for Laravel Framework 4, 5, 6, 7 and 8, Laravel Jetstream , Laravel Fortify, Laravel Cashier and Laravel Nova.

Laravel Lang In this repository, you can find the lang files for the Laravel Framework 4/5/6/7/8, Laravel Jetstream , Laravel Fortify, Laravel Cashier

Aug 7, 2022
Provides support for message translation and localization for dates and numbers.

The I18n library provides a I18n service locator that can be used for setting the current locale, building translation bundles and translating messages. Additionally, it provides the Time and Number classes which can be used to output dates, currencies and any numbers in the right format for the specified locale.

Jul 8, 2022
A morphological solution for Russian and English language written completely in PHP.
A morphological solution for Russian and English language written completely in PHP.

Morphos A morphological solution for Russian and English language written completely in PHP. Tests & Quality: Features [✓] Inflection of Personal name

Jul 30, 2022
Easy multilingual urls and redirection support for the Laravel framework

Linguist - Multilingual urls and redirects for Laravel This package provides an easy multilingual urls and redirection support for the Laravel framewo

Jul 18, 2022
Automatically translate and review your content via Lokalise
Automatically translate and review your content via Lokalise

This extension will work as a bridge between Pimcore and Lokalise for the purpose of automating the whole translation workflow. Thus eliminating most of the manual steps in the task along with availing quality translation-review service from Lokalise.

Jan 10, 2022
Filament Translations - Manage your translation with DB and cache
Filament Translations - Manage your translation with DB and cache

Filament Translations Manage your translation with DB and cache, you can scan your languages tags like trans(), __(), and get the string inside and tr

Jul 27, 2022
A convenience package for php multilingual web applications
A convenience package for php multilingual web applications

PHP Translation Install Lifecycle Configuration Content of PHP File Content of Json File Content Of Database Table Use Of Array Or Json Database PHP T

Jul 7, 2022
Gettext is a PHP (^7.2) library to import/export/edit gettext from PO, MO, PHP, JS files, etc.

Gettext Note: this is the documentation of the new 5.x version. Go to 4.x branch if you're looking for the old 4.x version Created by Oscar Otero http

Jul 30, 2022
PHP code scanner to use with gettext/gettext

PHP code scanner to use with gettext/gettext

Jul 28, 2022
Javascript code scanner to use with gettext/gettext

Javascript code scanner to use with gettext/gettext

Feb 14, 2022
JSONFinder - a library that can find json values in a mixed text or html documents, can filter and search the json tree, and converts php objects to json without 'ext-json' extension.

JSONFinder - a library that can find json values in a mixed text or html documents, can filter and search the json tree, and converts php objects to json without 'ext-json' extension.

Apr 15, 2022