Locale::TextDomain::OO but some modules using Locale::Maketext‎‎‎

About

Overview

.==-.                   .-==.
 \()8`-._  `.   .'  _.-'8()/
 (88"   ::.  \./  .::   "88)
  \_.'`-::::.(#).::::-'`._/
    `._... .q(_)p. ..._.'
      ""-..-'|=|`-..-""
      .""' .'|=|`. `"".
    ,':8(o)./|=|\.(o)8:`.
   (O :8 ::/ \_/ \:: 8: O)
    \O `::/       \::' O/
     ""--'         `--""   hjw
  Lexicon Translator Plugins

Main part: Locale::TextDomain::OO

Overview Lexicon

                        .-==.
                .'  _.-'8()/
             \./  .::   "88)
             (#).::::-'`._/
             (_)p. ..._.'
             |=|`-..-""
             |=|`. `"".
             |=|\.(o)8:`.
             \_/ \:: 8: O)
                  \::' O/
                   `--""   hjw
           Lexicon Plugins

Lexicon

Simple project

... = Locale::TextDomain::OO->instance(
  plugins => [ ... ],
);

Customized project

Customized project

... = Locale::TextDomain::OO->new(
  plugins  => [ ... ],
  language => '...', # set on different ways
                     # normally note here
  category => '...', # see also loc_c
  domain   => '...', # see also loc_d
  project  => '...', # to create multiple singletons
  filter   => sub { ... }, # running after translation
);

Customized project

Ends with something like

MyProject::I18N->instance;

Translate HTML

HTML as non HTML

Escape the whole translation

#. { search_argument => $code // q{} },
#: lib/.../Appointments.pm:296
#: lib/.../Appointments.pm:334
msgid "No results for [{search_argument}]"
msgstr "Keine Ergebnisse für [{search_argument}] gefunden"

Minimize HTML

Easy to prevent broken HTML

#: lib/.../Survey.pm:57
msgid "Write a <a>experience review</a> now"
msgstr "Schreiben Sie jetzt einen <a>Erlebnis-Bericht</a>"

Phrase is HTML

Escape placeholders

#. experience => $event->name
#: lib/.../Survey.pm:53
msgid ""
"Did you already attend your experience"
" <b>{experience :html}</b>?"
msgstr ""
"Haben Sie schon an Ihrem Erlebnis"
" <b>{experience :html}</b> teilgenommen?"

Tag all placeholders

Except simple text for easier translation

#. end => $voucher_end, start => $start_param,
#: lib/.../Create.pm:100
msgid ""
"The voucher end date [{end :date}]"
" expires before the chosen appointment [{start :date}]."
msgstr ""
"Das Gutschein-Enddatum [{end :date}]"
" läuft vor dem gewählten Termin [{start :date}] ab."

Build a customized project

package MyProject::I18N;

sub new {
  my $ltdoo = Locale::TextDomain::OO->new(
    plugins => [ qw(
      Language::LanguageOfLanguages
      Expand::Gettext::Loc
      Expand::Maketext::Loc
    ) ],

Register a logger

    logger => sub {
      my ($message, $arg_ref) = @_;
      my $type = $arg_ref->{type};
      MyLogger->$type($message);
      return;
    },

Register a filter

    filter => sub {
      my ($self, $string_ref) = @_;
      # Maketext: remove escape char ~ from ~, [ and ]
      ${$string_ref} =~ s{ [~] ( [~\[\]] ) }{$1}xmsg;
      return;
    },
  );

Config plugins

  for ( $ltdoo->expand_gettext_loc, $ltdoo->expand_maketext_loc ) {
    $_->modifier_code(
      sub {
        my ( $value, $attribute ) = @_;
        return __PACKAGE__->loc_format(
          $ltdoo, $value, $attribute,
        );
      },
    );
  };

  return $ltdoo;
}

Call loc_format from everywhere

sub loc_format {
  # numf, numf0 .. numfN, numf00 .. numf0N
  ... format numeric placeholder

  # nump, nump0 .. numpN, nump00 .. nump0N, numpf
  # numpEUR, nump0EUR .. numpNEUR, nump00EUR .. nump0NEUR, numpfEUR
  ... format price placeholder

  # html, javascript
  ... run escape
}

Why loc_round?

...->loc_nx(
  q{{number :numf2} foo},
  q{{number :numf2} foo's},
  # switch between plural forms
  number => ...->loc_round($number, ':numf2'),
);

Call loc_round from everywhere

sub loc_round {
  # numf, numf0 .. numfN, numf00 .. numf0N
  ... format numeric placeholder

  # nump,    nump0    .. numpN,    nump00    .. nump0N,    numpf
  # numpEUR, nump0EUR .. numpNEUR, nump00EUR .. nump0NEUR, numpfEUR
  ... format price placeholder
}

Locale::TextDomain::OO::Util::JoinSplitLexiconKeys

Some lexicon key parts are used during translation

Split multiple projects in lexicon hash

Load lexicon

Some short helpers

my $key_util = Locale::TextDomain::OO::Util::JoinSplitLexiconKeys
  ->instance;
my $jlk = fun ($language) {
  $key_util->join_lexicon_key({ language => $language });
};
my $jlk_c = fun ($language, $category) {
  $key_util->join_lexicon_key({ category => $category,
                                language => $language });
};

Load 1st lexicon from mo files

Locale::TextDomain::OO::Lexicon::File::MO
  ->new
  ->lexicon_ref({
    search_dirs => [ MyConfig->i18n_path ],
    data        => [
      $jlk->( q{*} ) => '*.mo',
      merge_lexicon  => $jlk->('de'), $jlk->('de-at')
        => $jlk->('de-at'),
      merge_lexicon  => $jlk->('de'), $jlk->('de-ch')
        => $jlk->('de-ch'),
    ],
    decode     => 1,
  });

Load 2nd lexicon for data translation

my $lexicon_po = Locale::TextDomain::OO::Lexicon::File::PO->new;
my @children = MyConfig
  ->i18n_path
  ->children( __PACKAGE__->language_cached_regex );
for my $child (@children) {
  my $category = $child->relative( MyConfig->i18n_path )->stringify;
  $lexicon_po
    ->lexicon_ref({
      search_dirs => [ $child ],
      data        => [ $jlk_c->( q{*}, $category ) => '*.po' ],
      gettext_to_maketext
                  => 1,
    });
}

Load 3rd lexicon for hardcoded Maketext in module

my @mappings = (
  # hfh lexicons of version 0.40056
  { hfh => 'de_de', language => 'de', plural_forms => 'nplurals=2; plural=(n != 1);' },
  { hfh => 'en_us', language => 'en', plural_forms => 'nplurals=2; plural=(n != 1);' },
  { hfh => 'ru_ru', language => 'ru', plural_forms => 'nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);' },
  # map non hfh languages to hfh en because used in project
  { hfh => 'en_us', language => 'cs', plural_forms => 'nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;' },
  { hfh => 'en_us', language => 'es', plural_forms => 'nplurals=2; plural=(n != 1);' },
  { hfh => 'en_us', language => 'fr', plural_forms => 'nplurals=2; plural=(n > 1);' },
);

Preapre 3rd lexicon

my $package = load_class(
  "HTML::FormHandler::I18N::$map_ref->{hfh}",
);
my %package_lexicon = %{"${package}::Lexicon"};
delete $package_lexicon{_AUTO};
$lexicon_of{
  $jlk_c->(
    $map_ref->{language},
    'HTML_FormHandler',
  )
} = [ ... ]; # header and messages

Store 3rd lexicon

Locale::TextDomain::OO::Lexicon::Hash
  ->new
  ->lexicon_ref(\%lexicon_of);

Merge region lexicons

my $lexicon = Locale::TextDomain::OO::Singleton::Lexicon
  ->instance;
$lexicon
  ->merge_lexicon(
    $jlk_c->('de', 'HTML_FormHandler'), $jlk->('de-at')
      => $jlk_c->('de-at', 'HTML_FormHandler'),
    )
  ->merge_lexicon(
    $jlk_c->('de', 'HTML_FormHandler'), $jlk->('de-ch')
      => $jlk_c->('de-ch', 'HTML_FormHandler'),
  );

End

Thank you

http://download.steffen-winkler.de/gpw2018