NAME

Mojolicious::Plugin::Localize - Localization for Mojolicious

SYNOPSIS

# Register the plugin with a defined dictionary
plugin  Localize => {
  dict => {
    _  => sub { $_->locale },
    -de => {
      welcome => "Willkommen in <%=loc 'App_name' %>!",
      bye => 'Auf Wiedersehen!'
    },
    en => {
      welcome => "Welcome to <%=loc 'App_name' %>!",
      bye => 'Good bye!'
    },
    App => {
      name => {
        -long => 'Mojolicious',
        short => 'Mojo',
        land  => 'MojoLand'
      }
    }
  }
};

# Lookup dictionary entries from templates
%= loc 'welcome'

# If the user has a preferred locale of 'en',
# the output is 'Welcome to Mojolicious!'

DESCRIPTION

Mojolicious::Plugin::Localize is a localization framework for Mojolicious, heavily inspired by Mozilla's l20n. Instead of being a reimplementation it uses Mojo::Template for string interpolation, Mojolicious::Plugin::Config for dictionaries and helpers for template functions.

METHODS

Mojolicious::Plugin::Localize inherits all methods from Mojolicious::Plugin and implements the following new ones.

register

app->plugin(Localize => {
  dict => {
    _  => sub { $_->locale },
    de => {
      welcome => 'Willkommen!',
      bye => 'Auf Wiedersehen!',
    },
    en => {
      welcome => 'Welcome!',
      bye => 'Good bye!'
    }
  },
  override  => 1,
  resources => ['myapp.fr.dict', 'myapp.pl.dict']
});

Called when registering the plugin.

Expects a parameter dict containing a localization dictionary. Further dictionary files to be loaded can be passed as an array reference using the resources parameter.

The plugin can be registered multiple times, and defined dictionaries will be merged.

Already existing key definitions won't be overridden in that way unless an additional override parameter is set to a true value. Dictionary entries from resource files, on the other hand, will always override, so the order of the given array is of relevance.

All parameters can be set either on registration or in a configuration file with the key Localize (loaded only on first registration).

HELPERS

In addition to the listed helpers, Mojolicious::Plugin::Localize loads further helpers by default, see quant and localize->locale.

loc

# Lookup a dictionary entry as a controller method
my $entry = $c->loc('welcome');

%# Lookup a dictionary entry in templates
%= loc 'welcome'
%= loc 'welcome', 'Welcome to the site!'
%= loc 'welcome', user => 'Peter'
%= loc 'welcome', 'Welcome to the site!', user => 'Peter'

Makes a dictionary lookup and returns a string.

Expects a dictionary key, an optional fallback message and optional stash values.

localize->dictionary

print $c->localize->dictionary->{welcome}->{en};

Nested helper in the localize namespace. Returns the merged dictionary hash.

Mojolicious::Plugin::Localize loads further plugins establishing nested helpers, see localize->locale.

localize->preference

# Dictionary:
# {
#   '_' => ['de','en'],
#   '-en' => {
#     welcome => 'Welcome'
#   },
#   'de' => {
#     welcome => 'Willkommen'
#   },
#   'pl' => {
#     welcome => 'Serdecznie witamy'
#   }
# }

print $c->localize->preference;
# 'de'

Return the prefered existing key for a given dictionary path. In case the first level of a dictionary path is a language code and the preferred keys are the user's preferred locales, this will return the preferred existing language code for a user.

This helper is EXPERIMENTAL!

COMMANDS

localize

$ perl app.pl localize en pl

Generate a new dictionary template for a certain locale based on an existing dictionary. See Mojolicious::Plugin::Localize::Command::localize for further information.

DICTIONARIES

{
  dict => {
    _ => sub { $_->locale },
    -en => {
      welcome => 'Welcome!'
    },
    de => {
      welcome => 'Willkommen!'
    }
  },
  resources => ['myapp.fr.dict']
};

# myapp.fr.dict
{
  fr => {
    welcome => 'Bonjour!'
  }
};

Dictionaries can be loaded by registering the plugin either as a passed dict value or in separated files using the resources parameter.

Notation

{
  en => {
    tree => {
      singular => 'Tree',
      plural => 'Trees'
  },
  de => {
    tree => {
      singular => 'Baum',
      plural => 'Bäume'
    }
  }
}

Dictionaries are nested hash references. On each level, there is a key that can either lead to a subdictionary or to a value.

{
  en => {
    welcome => 'Welcome!',
    greeting => '<%= loc "en_welcome" %> Nice to meet you, <%= $user %>!'
  },
  de => {
    welcome => 'Willkommen!',
    greeting => '<%= loc "de_welcome" %> Schön, Dich zu sehen, <%= $user %>!'
  }
}

Values are Mojo::Template strings (with default configuration) or code references (with the controller object passed when evaluating, followed by further parameters as a hash). In case a string is passed as a scalar reference, it won't be interpolated as a Mojo::Template.

As you see above, values may fetch further dictionary entries using the loc helper. To fetch entries from the dictionary using the loc helper, the user has to pass the key structure in so-called short notation, by adding underscores following they key's path. The short notation for the entry Bäume in the first example is de_tree_plural.

%= loc 'de_tree_plural'
%# 'Bäume'

The short notation can also be used to add new dictionary entries using dictionary files or the dict parameter of the plugins registration handler. The following dictionary definitions are therefore equal:

{
  de => {
    welcome => 'Willkommen!'
  }
};

# or
{
  de_welcome => 'Willkommen!'
};

There is no limitation for nesting of dictionary entries. The order in a dictionary is irrelevant as well.

Keys need to contain alphanumeric characters only, as special characters are reserved for later use.

Preferred Keys

The underscore is a special key, marking preferred keys on the dictionary level, in case no matching key can be found on that level (which is the case when a key in short notation is underspecified).

{
  welcome => {
    _ => 'en',
    de => 'Willkommen!'
    en => 'Welcome!'
  }
}

In case the key welcome_de is requested with the above dictionary established, the value Willkommen! will be returned. But if the underspecified key welcome is requested without a matching key on the final level, the preferred key en will be used instead, returning the value Welcome!.

Preferred keys can exist on any level of the nesting and are always called when there is no matching key as part of the short notation.

Preferred keys may contain the key as a string, a Mojo::Template, an array reference of keys (in order of preference), or a subroutine returning either a string or an array reference.

# The preferred key is 'en'
_ => 'en'

# The preferred key is the stash value of 'user_status' (e.g. 'mod' or 'admin')
_ => '<%= $user_status %>'
_ => sub { shift->stash('user_status') }

# The preferred key is 'en', and in case this isn't defined, it's 'de' etc.
_ => [qw/en de/]
_ => sub { [qw/en de/] }

The first parameter passed to subroutines is the controller object, and the local variable $_ is set to the nested helper object, which eases calls to, for example, the localize->locale helper

# The preferred key is based on the user agent's localization
_ => sub { $_->locale }

Preferred keys in short notation have a trailing underscore:

# Set the preferred key in nested notation:
{
  greeting => {
    _ => sub { $_->locale },
    en => 'Hello!',
    de => 'Hallo!'
  }
}

# Set the preferred key in short notation:
{
  greeting_ => sub { $_->locale },
  greeting_en => 'Hello!',
  greeting_de => 'Hallo!'
}

Default Keys

The dash symbol is a special key, marking default keys on the dictionary level, in case no matching or preferred key can be found on that level. They can be given in addition to preferred keys.

{
  welcome => {
    _   => 'pl',
    '-' => 'en',
    en  => 'Welcome!',
    de  => 'Willkommen!'
  }
}

In case the key welcome_de is requested with the above dictionary established, the value Willkommen! will be returned. But if the underspecified key welcome is requested without a matching key on the final level, and the preferred key pl isn't defined in another dictionary, the default key en will be used instead, returning the value Welcome!.

Default keys can be alternatively marked with a leading dash symbol.

{
  welcome => {
    _   => 'pl',
    -en => 'Welcome!',
    de  => 'Willkommen!'
  }
}

To define default keys in short notation, prepend a dash to each subkey in question.

{
  'welcome_-en' => 'Welcome!',
  'welcome_de'  => 'Willkomen!'
}

Preferred and default keys are specific to subtrees. That means in the following dictionary loc('title') will return the string My Sojolicious for the locale en and nothing for the locale de, as no matching path is found. In case there is a list of locales like de,en, the call will trigger backtracking and return My Sojolicious as well.

{
  _ => sub { $_->locale }
  en => {
    title => {
      -short => 'My Sojolicious',
      desc => 'A federated social web toolkit'
    }
  },
  de => {
    title => {
      short => 'Mein Sojolicious',
      desc => 'Ein Werkzeugkasten für das Social Web'
    }
  }
}

To return Mein Sojolicious in case of loc('title') for the locale de, the second short key needs to be prefixed as well.

End Keys

The period sign is a special key, marking an end value on the final dictionary level. This prevents preferred and default keys to be searched, when the key is already consumed. End keys can only point to values.

{
  welcome => {
    '.' => 'Welcome!!!',
    _ => [qw/en de/],
    de => 'Willkommen!',
    en => 'Welcome!'
  }
}

Here the key welcome will return the value Welcome!!!, while welcome_de will return Willkommen! and welcome_pl will return Welcome!.

Forcing Preferred and Default Keys

{
  Lang => {
    _ => [qw/en de pl/],
    -en => {
      de => 'German',
      en => 'English'
    },
    de => {
      de => 'Deutsch',
      en => 'Englisch'
    }
  }
}

When looking up an entry in the dictionary tree, the consumption precedence is primary > preferred > default.

But in rare occasions a lookup has to force the usage of preferred or default keys over primary key access, for example, in the above dictionary a call to Lang_de, expecting the value German, will fail, as the de will be consumed on the second level and will therefore be missing on the third. To force the usage of the preferred or the default key on the second level, simply prepend another underscore to the second partial key (to consume an empty partial key) and call Lang__de with the expected result.

Hints and Conventions

Mojolicious::Plugin::Localize let you decide, how to nest your dictionary entries. For internationalization purposes, it is a good idea to have the language key on the first level, so you can establish further entries relying on that structure (see, e.g., the example snippet in SYNOPSIS).

Instead of passing default messages using the loc helper, you should always define default dictionary entries.

Dictionary keys should always be lower case, and plugins, that provide their own dictionaries, should prefix their keys with a namespace (e.g. the plugin's name) in camel case, to prevent clashes with other dictionary entries. For example the welcome message for this plugin should be named Localize_welcome.

Template files can be registered as dictionary keys to be looked up for rendering.

# Create dictionary keys for templates
{
  Template => {
    _ => sub { $_->locale },
    -en => {
      start => 'en/start'
    },
    de => {
      start => 'de/start'
    }
  }
}

# Lookup dictionary entry for rendering
$c->render($c->loc('Template_start'), variant => 'mobile');

TODO

AVAILABILITY

https://github.com/Akron/Mojolicious-Plugin-Localize

COPYRIGHT AND LICENSE

Copyright (C) 2014-2026, Nils Diewald.

This program is free software, you can redistribute it and/or modify it under the terms of the Artistic License version 2.0.