The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Locale::TextDomain::OO - Perl OO Interface to Uniforum Message Translation

$Id: OO.pm 551 2014-11-01 06:46:51Z steffenw $

$HeadURL: svn+ssh://steffenw@svn.code.sf.net/p/perl-gettext-oo/code/module/trunk/lib/Locale/TextDomain/OO.pm $

VERSION

1.016

Starting with version 1.000 the interface has changed.

DESCRIPTION

This module provides a high-level interface to Perl message translation.

Why a new module?

This module is similar to Locale::TextDomain and Locale::Maketext.

This module is not using/changing any system locale like Locale::TextDomain.

This module has no magic in how to get the language like Locale::Maketext. You decide what you need.

There are some plugins, so it is possible to use the maketext and/or getext style.

Locale::TextDomain::OO has a flexible object oriented interface based on Moo.

Creating the Lexicon and translating are two splitted things. So it is possible to create the lexicon during the initialisation phase. The connection between both is the singleton mechanimsm of the lexicon module.

How to extract?

Use module Locale::TextDomain::OO::Extract. This is a base class for all source scanner to create pot files. Use this base class and give this module the rules or use one of the already exteded classes. Locale::TextDomain::OO::Extract::Perl is a extension for Perl code and so on.

Do not follow the dead end of Locale::Maketext!

What is the problem of?

  • Locale::Maketext allows 2 plural forms (and zero) only. This is changable, but the developer has to control the plural forms. He is not an omniscient translator.

    Gettext allows as much as needed plural forms at destination language.

  • 'quant' inside a phrase is the end of the automatic translation because quant is an 'or'-construct.

        begin of phrase [quant,_1,singular,plural,zero] end of phrase

    Gettext used full qualified sentences.

  • The plural form is allowed after a number, followed by a whitespace, not a non-breaking whitespace.

        1 book
        2 books

    A plural form can not be before a number.

        It is 1 book.
        These are 2 books.

    Gettext used full qualified sentences.

  • There is no plural form without a number in the phrase.

        I like this book.
        I like these books.

    Gettext used an extra count to select the plural form. Placeholders are placeholders not combined things.

  • Placeholders are numbered serially. It is difficult to translate this because the sense of the phrase could be lost.

        Still [_1] [_2] to [_3].
    
        Still 5 hours to midnight.
        Still 15 days to Olympics.

    Locale::TextDomain::OO used named placeholders like

        Still {count :num} {datetime unit} to {event}.
  • But there are lots of modules around Locale::Maketext.

This is the reason for another module to have:

  • Endless (real: up to 6) plural forms controlled by the translater and not by the developer.

  • Named placeholders.

More informations

Run the examples of this distribution (folder example).

Overview

 Application calls         Application calls   Application calls
 gettext methods           getext and          maketext methods
 (hint: select this)       maketext methods            |
         |                               |             |
         v                               v             v
 .-----------------------------------------------------------------.
 | Locale::TextDomain::OO                                          |
 | with plugin LanguageOfLanguages                                 |
 | with plugins Locale::TextDomain::OO::Plugin::Expand::...        |
 |-----------------------------------------------------------------|
 | Gettext                          |\       /| Maketext           |
 | Gettext::DomainAndCategory       | |     | | Maketext::Loc      |
 | Gettext::Loc (hint: select this) | > and < | Maketext::Localise |
 | Gettext::Loc::DomainAndCategory  | |     | | Maketext::Localize |
 | Gettext::Named                   |/       \|                    |
 `-----------------------------------------------------------------'
                            ^
                            |
 .--------------------------'-----------------.
 | Locale::TextDomain::OO::Singleton::Lexicon |------------------------.
 `--------------------------------------------'                        |
                            ^                                          |
                            |                                          |
 .--------------------------'-------------------------------.          |
 | build lexicon using Locale::TextDomain::OO::Lexicon::... |          |
 |----------------------------------------------------------|          |
 |           Hash              |   File::MO     | File::PO  |          |
 `----------------------------------------------------------'          |
       ^               ^               ^              ^                |
       |               |               |              |                |
 .-----'-----.    _____|_____    .-----'----.       .-----'----.       |
 | Perl      |   /_ _ _ _ _ _\   | mo files |-.     | po files |-.     |
 | data      |   |           |   `----------' |-.   `----------' |-.   |
 | structure |   | Database  |     `----------' |     `----------' |   |
 `-----------'   `-----------'       `----------'       `----------'   |
                                       ^                   ^           |
                                       |                   |           |
                                  build using        build using       |
                                  gettext tools      gettext tools     |
                                                                       |
                                  .------------------------------------'
                                  |
                                  v
 .------------------------------------------------------------------.
 | build JSON lexicon using Locale::TextDomain::OO::Lexicon::ToJSON |
 |------------------------------------------------------------------|
 |      to_json      |      to_javascript      |      to_html       |
 `------------------------------------------------------------------'
                                  |
                                  v
              .---------------------------------------.
              | var localeTextDomainOOLexicon = json; |
              `---------------------------------------'
                                  ^
                                  |
 .--------------------------------'-------------------------------------------------.
 | requires:                                                                        |
 | - http://jquery.com/                                                             |
 | - javascript/Locale/TextDomain/OO/Util/Constants.js                              |
 | - javascript/Locale/TextDomain/OO/Util/JoinSplitLexiconKeys.js                   |
 | - javascript/Locale/Utils/PlaceholderNamed.js                                    |
 |                                                                                  |
 | implemented:                                                                     |
 | javascript/Locale/TextDomain/OO.js                                               |
 | javascript/Locale/TextDomain/OO/Plugin/Expand/Gettext.js                         |
 | javascript/Locale/TextDomain/OO/Plugin/Expand/Gettext/DomainAndCategory.js       |
 | javascript/Locale/TextDomain/OO/Plugin/Expand/Gettext/Loc.js (hint: select this) |
 | javascript/Locale/TextDomain/OO/Plugin/Expand/Gettext/Loc/DomainAndCategory.js   |
 |                                                                                  |
 | not implemented:                                                                 |
 | javascript/Locale/TextDomain/OO/Expand/Maketext.js                               |
 | javascript/Locale/TextDomain/OO/Expand/Maketext/Loc.js                           |
 | javascript/Locale/TextDomain/OO/Expand/Maketext/Localise.js                      |
 | javascript/Locale/TextDomain/OO/Expand/Maketext/Localize.js                      |
 |                                                                                  |
 | Example:                                                                         |
 | javascript/Example.html                                                          |
 `----------------------------------------------------------------------------------'
         ^                     ^                  ^
         |                     |                  |
 JavaScript calls      JavaScript calls   JavaScript calls
 gettext methods       getext and         maketext methods
 (hint: select this)   maketext methods   (not implemented)
                       (not implemented)

SYNOPSIS

    require Locale::TextDomain::OO;
    my $loc = Locale::TextDomain::OO->new(
        # all parameters are optional
        plugins  => [ qw(
            Expand::Gettext::Loc
            +My::Special::Plugin
        ) ],
        language => 'de',          # default is i-default
        category => 'LC_MESSAGES', # default is q{}
        domain   => 'MyDomain',    # default is q{}
        filter   => sub {
            my ($self, $translation_ref) = @_;
            # encode if needed
            # run a formatter if needed, e.g.
            ${$translation_ref} =~ s{__ ( .+? ) __}{<b>$1</b>}xmsg;
            return;
        },
        logger   => sub {
            my ($message, $arg_ref) = @_;
            my $type = $arg_ref->{type}; # debug or warn
            Log::Log4perl->get_logger(...)->$type($message);
            return;
        },
    );

This configuration would be use Lexicon "de:LC_MESSAGES:MyDomain". That lexicon should be filled with data.

as singleton

Instead of method new call method instance to get a singleton.

    my $instance = Locale::TextDomain::OO->instance(
        # Same parameters like new,
        # Initialization on first call only.
    );
    $same_instance = Locale::TextDomain::OO->instance;

Attributes handled with plugin Expand::Gettext

This plugin can handle named placeholders like {name}. For numeric placeholders add attribute :num. That allows easier automatic translation and to localize numbers. E.g. write {books :num} to msgid, msgid_plural.

Grammar rules for string placeholders are able to handle affixes. The translation office can add attributes, e.g. {town :accusative} to msgstr, msgstr[n].

Add the modifier code like that to handle that attributes.

    $loc->expand_gettext->modifier_code(
        sub {
            my ( $value, $attribute ) = @_;
            if ( $loc->language eq 'ru' ) {
                if ( $attribute eq 'accusative' ) {
                   ...
                }
            }
            elsif ( $loc->language eq 'de' }xms ) {
                if ( $attribute eq 'numf' ) {
                    ...
                    $value =~ tr{.,}{,.};
                }
            }
            return $value;
        },
    );

Attributes handled with plugin Expand::Gettext::Loc

Like before but a little difference. Instead of

    $loc->expand_gettext->modifier_code(...

write

    $loc->expand_gettext_loc->modifier_code(...

Attributes handled with plugin Expand::Gettext::Loc

Like before but a little difference. Instead of

    $loc->expand_gettext->modifier_code(...

write

    $loc->expand_gettext_named->modifier_code(...

Localize numbers with plugin Expand::Maketext

Add the code to localize numbers.

    $loc->expand_maketext->formatter_code(
        sub {
            my $value = shift;
            if ( $loc->language eq 'de' ) {
                ...
                $value =~ tr{.,}{,.};
            }
            return $value;
        },
    );

JAVASCRIPT

How to use the JavaScript framework see Locale::TextDomain::OO::JavaScript.

SUBROUTINES/METHODS

method new

see SYNOPSIS

method instance

When using the singleton mechanism, the object need not be transported through all the subroutines.

Instead of

    my $loc = Locale::TextDomain::OO->new(...);
    ...
    $loc->any_method(...);

write

    Locale::TextDomain::OO->instance(...);
    ...
    my $loc = Locale::TextDomain::OO->instance;
    $loc->any_method(...);

In case of webserver child or similar, set the language for every reqeuest not as parameter of the first instance call.

method language

Set the language an prepare the translation. You know exactly how to set. This module is stupid.

    $loc->language( $language );

Get back

    $language = $loc->language;

method category

You are able to ignore or set the category. That depends on your project.

    $loc->category($category || q{} );
    $category = $loc->category;

method domain

You are able to ignore or set the domain. That depends on your project.

    $loc->domain($domain || q{} );
    $domain = $loc->domain;

method filter

You are allowed to run code after each translation.

    $loc->filter( sub {
        my ( $self, $translation_ref ) = @_;

        # $self is $loc
        # manipulate ${$translation_ref}
        # do not undef ${$translation_ref}

        return;
    } );

Switch off the filter

    $loc->filter(undef);

method logger

Set the logger

    $loc->logger(
        sub {
            my ($message, $arg_ref) = @_;
            my $type = $arg_ref->{type};
            Log::Log4perl->get_logger(...)->$type($message);
            return;
        },
    );

$arg_ref contains

    object => $loc, # the object itself
    type   => 'debug', # or 'warn'
    event  => 'language,selection', # 'language,selection,fallback'
                                    # or 'translation,fallback'

method translate

Do never call that method in your project. This method was called from expand plugins only.

    $translation
        = $self->translate($msgctxt, $msgid, $msgid_plural, $count, $is_n);

method run_filter

Do never call that method in your project. This method was called from expand plugins only.

    $self->filter(\$translation);

EXAMPLE

Inside of this distribution is a directory named example. Read the file README there. Then run the *.pl files.

DIAGNOSTICS

confess

CONFIGURATION AND ENVIRONMENT

none

DEPENDENCIES

Locale::TextDomain::OO::Translator

INCOMPATIBILITIES

not known

BUGS AND LIMITATIONS

In the gettext manual you can read at "15.5.18.9 Bugs, Pitfalls, And Things That Do Not Work" something that is not working with Perl. The examples there are rewritten and explained here.

string interpolation and joined strings

    print <<"EOF";
        $loc->__(
            'The dot operator'
            . ' does not work'
            . ' here!'
        )
        Likewise, you cannot @{[ $loc->__('interpolate function calls') ]}
        inside quoted strings or quote-like expressions.
    EOF

The fist call can not work. Methods are not callable in interpolated strings/"here documents". The . operator is normally not implemented at the extractor. The first parameter of method __ must be a constant.

There is no problem for the second call because the extractor extracts the Perl file as text and did not parse the code.

Regex eval

This example is no problem here, because the file is extracted as text.

    s/<!--START_OF_WEEK-->/$loc->__('Sunday')/e;

named placeholders

Method __ is an alias for method __x. But {OPTIONS} is not a placeholder because key "OPTIONS" is not in parameters.

    die $loc->__("usage: $0 {OPTIONS} FILENAME...\n");

    die $loc->__x("usage: {program} {OPTIONS} FILENAME...\n", program => $0);

SEE ALSO

Locale::TextDoamin

Locale::Maketext

http://www.gnu.org/software/gettext/manual/gettext.html

http://en.wikipedia.org/wiki/Gettext

http://translate.sourceforge.net/wiki/l10n/pluralforms

http://rassie.org/archives/247 The choice of the right module for the translation.

AUTHOR

Steffen Winkler

LICENSE AND COPYRIGHT

Copyright (c) 2009 - 2014, Steffen Winkler <steffenw at cpan.org>. All rights reserved.

This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself.