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

Overview

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.

Comments
  • Add test for some PHP special chars

    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 ""
    
    opened by mlocati 20
  • Native (GettextTranslator) translator does not work with php 5.5

    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?

    opened by tarampampam 16
  • WIP: Twig AST extractor for Timber

    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.

    opened by drzraf 15
  • Empty translation prepends generated PO

    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?

    opened by marcandrews 15
  • Extraction of variables and concatenated strings

    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.

    opened by mlocati 13
  • Plural rules definition

    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.

    opened by mlocati 13
  • Faster extraction

    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.

    opened by mlocati 13
  • Add support for XLIFF unit IDs

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

    opened by asmecher 11
  • Message uniqueness

    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
    opened by mlocati 11
  • I need help configuring a gettext project

    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.

    opened by NathanBnm 10
  • Add support for translation extraction from VueJs templates

    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.

    opened by briedis 10
  • [question] How to store arbitrary key-value payload in Translation objects, now that it can no longer be faked using references?

    [question] How to store arbitrary key-value payload in Translation objects, now that it can no longer be faked using references?

    In previous versions, one could use $translation->addReference() to store arbitrary key-value payload in Translation objects. Since the $line parameter wasn't typehinted, one could use arbitrary string keys, turning the references into a key-value store.

    In more current versions, to which I am trying to upgrade, the API is $translation->getReferences()->add(), and the ->add() method requires that the $line parameter be an int. Code that used references as payload store can no longer work.

    What is the recommended way to store arbitrary key-value payload in Translation objects now?

    If I have a .po file with non-string lines in references, created by previous version of my code, will the current version of this library be able to load these?

    opened by rulatir 1
  • translations findById

    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,

    opened by ouhman 2
  • Make use of msgctxt

    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,

    opened by limogin 1
  • Gettext doesn`t extract template literals from JS

    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.

    opened by DaweTaller 1
  • How to load multiple *.mo files (general and for specific textdomain)

    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.

    opened by jasomdotnet 3
  • Why PHP >= 7.2 for Gettext v5?

    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?

    opened by drzraf 1
Releases(v4.8.8)
Owner
Gettext
PHP implementation of gettext
Gettext
Magento-bulk - Bulk Import/Export helper scripts and CLI utilities for Magento Commerce

Magento Bulk Bulk operations for Magento. Configuration Copy config.php.sample to config.php and edit it. Product Attribute Management List All Attrib

Bippo Indonesia 23 Dec 20, 2022
Improve default Magento 2 Import / Export features - cron jobs, CSV , XML , JSON , Excel

Improve default Magento 2 Import / Export features - cron jobs, CSV , XML , JSON , Excel , mapping of any format, Google Sheet, data and price modification, improved speed and a lot more!

Firebear Studio 173 Dec 17, 2022
Import/Export configuration data in Magento 2 via CLI.

ConfigImportExport This module provides new CLI commands for Magento 2 to import/export data in/from core_config_data. This module is inspired by the

semaio 135 Dec 9, 2022
A tool that allows to quickly export data from Magento 1 and Magento 2 store and import it back into Magento 2

Simple Import / Export tool A tool that allows to quickly export data from Magento 1 and Magento 2 store and import it back into Magento 2. Table data

EcomDev B.V. 51 Dec 5, 2022
Import/Export configuration data in Magento 2 via CLI.

ConfigImportExport This module provides new CLI commands for Magento 2 to import/export data in/from core_config_data. This module is inspired by the

semaio 117 Mar 23, 2022
Magento 2 - Improved Import / Export extension

Improve default Magento 2 Import / Export features - cron jobs, CSV , XML , JSON , Excel , mapping of any format, Google Sheet, data and price modification, improved speed and a lot more!

Firebear Studio 173 Dec 17, 2022
Import data from and export data to a range of different file formats and media

Ddeboer Data Import library This library has been renamed to PortPHP and will be deprecated. Please use PortPHP instead. Introduction This PHP library

David de Boer 570 Dec 27, 2022
Multi-language field export/import tool for ProcessWire

Language field export/import for ProcessWire Typically the way you translate page field values in ProcessWire is to edit a page, view the text in one

Ryan Cramer 3 Aug 19, 2022
Provide CSV, JSON, XML and YAML files as an Import Source for the Icinga Director and optionally ship hand-crafted additional Icinga2 config files

Icinga Web 2 Fileshipper module The main purpose of this module is to extend Icinga Director using some of it's exported hooks. Based on them it offer

Icinga 25 Sep 18, 2022
Tool for easy selection and export of user files in ZIP format.

Personal data export Idea Tool for easy selection and export of user files in ZIP format. Within a single selector, you choose all user data (much of

Baraja packages 2 Oct 18, 2021
It is a simple blog application coded with PHP, HTML, CSS. You can develop, edit. You can see it as a skeleton. ⚡

PHP-BLOG-SYSTEM Simple blog system Features Adding Text Update Text Text Deletion User Login and register Bootstrap Design Profile Page How to use blo

Selçuk 2 Aug 21, 2022
Laravel-htaccess - a package for dynamically edit .htaccess in laravel

laravel-htaccess a package for dynamically edit .htaccess in laravel use RedirectHtaccess Facade function for add RedirectHtaccess()->add(); return in

Mohammad khazaee 3 Dec 19, 2021
Ied plugin composer - Inspired Plugin Composer: Create, publish and edit plugins from within Textpattern CMS.

ied_plugin_composer Create, publish and edit plugins from within Textpattern CMS. Creates a new page under the Extensions tab where you can edit and e

Stef Dawson 8 Oct 3, 2020
CRUD Build a system to insert student name information, grade the class name, and edit and delete this information

CRUD Build a system to insert student name information, grade the class name, and edit and delete this information

Sajjad 2 Aug 14, 2022
A plugin that helps you to edit item's NBT in-game

NBTEditor A plugin that helps you to edit item's NBT in-game Usage: Join your server and enter command /nbteditor (or /nbte). The editor will be displ

null 4 Dec 7, 2022
A plugin to make life easier for users who need to edit specific functions of a world and also create, rename and delete worlds quickly using commands or the world management menu.

A plugin to make life easier for users who need to edit specific functions of a world and also create, rename and delete worlds quickly using commands or the world management menu.

ImperaZim 0 Nov 6, 2022
PHP 7+ Payment processing library. It offers everything you need to work with payments: Credit card & offsite purchasing, subscriptions, payouts etc. - provided by Forma-Pro

Supporting Payum Payum is an MIT-licensed open source project with its ongoing development made possible entirely by the support of community and our

Payum 1.7k Dec 27, 2022
A comprehensive library for generating differences between two strings in multiple formats (unified, side by side HTML etc). Based on the difflib implementation in Python

PHP Diff Class Introduction A comprehensive library for generating differences between two hashable objects (strings or arrays). Generated differences

Chris Boulton 708 Dec 25, 2022
Import your Foursquare/Swarm checkins to your Micropub-enabled website

Swarm Checkins Import Import your Foursquare/Swarm checkins to your Micropub-enabled website Installation You'll need PHP and Composer to install this

Aaron Parecki 20 Dec 24, 2022