Locale:...ain::OO(3)

πŸ“¦
version 1.036 Β· 2 versions
, in fedora 43

hey bestie, this is part 1 of 3 of Locale::TextDomain::OO(3) β€” the Perl OO interface to Uniforum message translation, straight outta the User Contributed Perl Documentation section πŸ’–


πŸ’– NAME πŸ’–

Locale::TextDomain::OO β€” a Perl OO interface to Uniforum message translation. periodt. ✨

Also, the source has these iconic metadata lines, bestie, and we're keeping them exactly:

$Id: OO.pm 724 2018-12-24 11:50:32Z steffenw $

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

🌸 VERSION 🌸

1.036

Starting with version 1.000 the interface has changed. Not a drill. The interface said "new era, new me" πŸ’…


✨ DESCRIPTION ✨

Okay so this module is giving high-level interface to Perl message translation. It's based on Singleton objects for translator and lexicon. So it's easy to attach that without any Web or Cmd plugins. There is no conflict between singleton and more than 1 project at the same time. The lexicon has a project key to split. 🫢

You are able to use the translator singleton earlier then language initialization β€” but do not call the translation methods before. Create the lexicon singleton during compilation time. ⏰

Maketext is implemented here for moving existing projects to Gettext.

BabelFish is implemented for multi-plural in one phrase. But use BabelFish in that case only. BabelFish is much harder to translate (similar to maketext) by translation office or automatic translation. 😭

For combinded plurals watch the Gettext multiplural example. In that case you have to write that awful long formulas but it is possibel. πŸ’€

Never use keys instead of English language in code. You can't read, execute (with placeholders) or test your code without translation. fr fr.

πŸ¦‹ 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 different the Gettext, BabelFish and/or Maketext styles. πŸ”₯
  • Locale::TextDomain::OO has a flexible object oriented interface based on Moo.
  • Creating the Lexicon and translating are two split things. So it is possible to create the lexicon during the compilation phase. The connection between both is the singleton mechanism of the lexicon module using MooX::Singleton.

🌈 Plugin for the web framework? 🌈

This module was first used in a Catalyst based web project.

It was great to call everything on the Catalyst context object. It was great until we used module in web and shell scripts together and called the translation methods there.

What we have done was removing the Catalyst Maketext plugin. We are using a role in the Catalyst application and so we can call all the translation methods on the Catalyst context object like before. ✨

For the shell scripts we have a role for this Cmd's to call all the methods on $self.

In that roles we delegate a bunch of methods to Locale::TextDomain::OO. We also added our project specific methods there.

Write you own role. There is no need for a plugin for any framework. πŸ’…

πŸ‘‘ Who is sets the languages? πŸ‘‘

Not this module. periodt.

In a web application are lots of ideas to do that. But never we use the server settings, never.

The HTTP request contains that information. Extend this with super and panic languages. Normally you support only a bunch of world languages, so reduce. This module will find the first match.

Or you store and get the language in the session.

Or your application has a language selection menu. (Never use flags for that. There are multilanguage countries and more countries have the same language.) 🚫

🌸 How to extract the project files? 🌸

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

🧚 How to build the lexicon - without Gettext tools? 🧚

Use module Locale::TextDomain::OO::Extract::Process. This is able to read, strip, extract, merge, write and clean PO and MO files. πŸ’–

There is a module named Locale::TextDomain::OO::Util::JoinSplitLexiconKeys. That helps you to build the lexicon keys. Do not join the keys by yourself, it can break in future versions. Use the category, domain, language and project part to describe your lexicon. category, domain and language you can use during translation. The only reason for project is to separate your different lexicons. πŸ¦‹

πŸ’œ How to use the lexicon? πŸ’œ

The lexicons or all lexicons are stored in memory. The idea to build a combined lexicon is overwriting hash values by same key. So read and overwrite common stuff with specials. In an example case the region file en-gb.mo contains only the differences. The common file en.mo contains all messages. So it is possible to create full lexicons for any language and region. What you have to describe is: Where are the files - directories. Pick up the common file en.mo as en. Pick up the common file en.mo and the special en-gb.mo as en-gb. 🌈

πŸ’€ Do not follow the dead end of Locale::Maketext! πŸ’€

But it is allowed to use that writing for backward compatiblity.

What is the problem of Maketext? okay bestie, buckle up:

  • Locale::Maketext allows 2 plural forms (and zero) only. This is changeable, 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.

  • Maketext has a plural formula as placeholder in strings to translate. Gettext has fully readable strings with simple placeholders. Then a very good automatic translation is possible and this module helps you.

(Some projects have keywords instead of English language in code. This module helps you to write readable, uncrypic code. Programmers optimisation is a very bad idea for translations. Never split a sentence.)

  • '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 is using 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 translator and not by the developer.
  • Named placeholders.

⭐ More information ⭐

Run the examples of this distribution (folder example). 🫢


✨ Overview ✨

okay bestie, let's get into the whole architecture vibe. this diagram is literally the blueprint, no cap. it's giving "who calls whom" and "what plugins do what". take a deep breath, it's a lot but it's iconic. πŸ’…

   Overview
        Application calls           Application calls         Application calls
        Gettext methods             Gettext and               Maketext methods
        (Hint: select this)         Maketext methods              |
                |                   (Hint: Maketext already       |
                |                   exists in your project)       |
                |                               |                 |
                v                               v                 v
        .----------------------------------------------------------------------.
        | Locale::TextDomain::OO                                               |
        | with plugin LanguageOfLanguages                                      |
        | with plugins Locale::TextDomain::OO::Plugin::Expand::...             |
        |----------------------------------------------------------------------|
        | Gettext                           |\       /| Maketext               |
        | Gettext::DomainAndCategory        | |     | |                        |
        | Gettext::Loc (Hint: select this)  | |     | | Maketext::Loc          |
        | Gettext::Loc::DomainAndCategory   | > and < | (Hint: select this)    |
        | Gettext::Named (experimental)     | |     | |                        |
        | BabelFish::Loc                    | |     | | Maketext::Localise     |
        | BabelFish::Loc::DomainAndCategory |/       \| Maketext::Localize     |
        `----------------------------------------------------------------------'
                                   ^
                                   |
        .--------------------------'-----------------.
        | Locale::TextDomain::OO::Singleton::Lexicon |-------------------------.
        `--------------------------------------------'                         |
                                   ^                                           |
                                   |                                           |
        .--------------------------'-------------------------------.           |
        | build lexicon using Locale::TextDomain::OO::Lexicon::... |           |
        |----------------------------------------------------------|           |
        |           Hash              |   File::PO     | File::MO  |           |
        `----------------------------------------------------------'           |
              ^               ^               ^                  ^             |
              |               |               |                  |             |
        .-----'-----.    _____|_____    .-----'----.       .-----'----.        |
        | Perl      |   /_ _ _ _ _ _\   | po files |-.     | mo files |-.      |
        | data      |   |           |   `----------' |-.   `----------' |-.    |
        | structure |   | Database  |     `----------' |     `----------' |    |
        `-----------'   `-----------'       `----------'       `----------'    |
              ^                               ^   ^              ^             |
              |                               |   |              |             |
              |   .---------------------------'   |              |             |
         _____|___|_____                          |              |             |
        /_ _ _ _ _ _ _ _\   .----------------------------------------------.   |
        | Autotranslate |   | build using PO/MO                            |   |
        | Cache         |   | Locale::TextDomain::OO::Extract              |   |
        | CacheDatabase |   | and Locale::TextDomain::OO::Extract::Process |   |
        `---------------'   `----------------------------------------------'   |
                                                                               |
                                         .-------------------------------------'
                                         v
        .---------------------------------------------------------------------.
        | build JSON lexicon using Locale::TextDomain::OO::Lexicon::StoreJSON |
        |---------------------------------------------------------------------|
        |       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/BabelFish.js (under development)   |
        | 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       Gettext and                  Maketext methods
        (Hint: select this)   Maketext methods             (Maketext not implemented)
                              (Maketext not implemented)

so yeah, that's the whole map. now let's get into how you actually use it. πŸ’–

✨ SYNOPSIS ✨

here's the basic setup, bestie. it's giving "new" with all the optional params. 🌸

           require Locale::TextDomain::OO;
           use Log::Any qw($log);
           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{}
               project  => 'MyProject',   # default is undef
               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->$type($message);
                   return;
               },
           );

this config would use Lexicon "de:LC_MESSAGES:MyDomain::MyProject". that lexicon should be filled with data, periodt. πŸ’…

✨ as singleton ✨

if you want a singleton instead of calling new every time, just call instance. it initializes on the first call only, fr fr. πŸ¦‹

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

✨ Attributes handled e.g. with plugin Expand::Gettext::Loc ✨

so this plugin can handle named placeholders like "{name}". for numeric placeholders, add the attribute ":num". that makes automatic translation easier and lets you localize numbers. for example, write "{books :numf}" to msgid, msgid_plural. no cap. ✨

grammar rules for string placeholders can handle affixes. the translation office can add attributes, like "{town :accusative}" to msgstr, msgstr[n]. πŸ’–

add the modifier code like this to handle those 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 ✨

like before but a little difference. instead of

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

write

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

✨ Localize numbers with plugin Expand::Maketext ✨

add this 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 ✨

okay bestie, this is where the methods live πŸ’…

πŸ’– method new πŸ’–

new is like, see SYNOPSIS for the deets.

you literally cannot call method new with different plugins parameter. the first call will load the plugins. later calls set parameter plugins equal or do not set. periodt.

πŸ¦‹ method instance πŸ¦‹

when you're using the singleton mechanism, the object need not be transported through all the subroutines. it's giving main character energy in the best way.

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 request not as parameter of the first instance call. iykyk.

🌸 method language 🌸

set the language an prepare the translation. you know exactly how to set. this module is stupid (their words, not mine 😭).

           $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->$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. slay.

πŸ’€ 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 <<"EOT";
               $loc->loc_(
                   'The dot operator'
                   . ' does not work'
                   . ' here!'
               )
               Likewise, you cannot @{[ $loc->loc_('interpolate function calls') ]}
               inside quoted strings or quote-like expressions.
           EOT

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 loc_ must be a constant.

πŸ” Regex eval πŸ”

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

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

🏷️ named placeholders 🏷️

method loc_ is an alias for method loc_x. but {OPTIONS} is not a placeholder because key "OPTIONS" is not in parameters.

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

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

πŸ‘€ SEE ALSO πŸ‘€

✍️ AUTHOR ✍️

Steffen Winkler

       Copyright (c) 2009 - 2019, 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.

and that's a wrap, bestie! this page came from perl v5.42.0, dated 2025-07-25, and it's the one and only Locale:...ain::OO(3). go forth and translate iconically πŸ’–βœ¨πŸ¦‹

πŸ“¦ where this page comes from