Current section
Files
Jump to
Current section
Files
src/gettexter.erl
%%% @author Sergey Prokhorov <me@seriyps.ru>
%%% @author Emil Falk <emil.falk@textalk.se>
%% @copyright (C) 2014, Sergey Prokhorov
%% @copyright (C) 2014, Emil Falk
%% @doc
%% == gettexter ==
%%
%% The gettexter API exports functions similar to those offered by GNU gettext.
%% All lookup function works with both binaries and strings but not mixed. The
%% locale may be either atom, binary or string.
%%
%% == Macros ==
%% Several macros can be found in `include/shortcuts.hrl'. They are provided as
%% a convienient way to mark up your strings or binaries and to simplify
%% extraction via xgettext. All possible combinations from the API is provided.
%% To set a default domain for the macros with implicit domain below use the
%% constant GETTEXT_DOMAIN as for example:
%%
%% `-define(GETTEXT_DOMAIN, users).'
%%
%% === Locale in process dictionary ===
%%
%% ==== Domain from GETTEXT_DOMAIN ====
%% <ul>
%% <li>?_(Text)</li>
%% <li>?N_(Singular, Plural, N)</li>
%% <li>?P_(Context, Text)</li>
%% <li>?NP_(Context, Singular, Plural, N)</li>
%% </ul>
%%
%% ==== Domain as argument ====
%% <ul>
%% <li>?D_(Domain, Text)</li>
%% <li>?DN_(Domain, Singular, Plural, N)</li>
%% <li>?DP_(Domain, Context, Text)</li>
%% <li>?DNP_(Domain, Context, Singular, Plural, N)</li>
%% </ul>
%%
%% === Locale as argument ===
%%
%% ==== Domain from GETTEXT_DOMAIN ====
%% <ul>
%% <li>?_(Text, Locale)</li>
%% <li>?N_(Singular, Plural, N, Locale)</li>
%% <li>?P_(Context, Text, Locale)</li>
%% <li>?NP_(Context, Singular, Plural, N, Locale)</li>
%% </ul>
%%
%% ==== Domain as argument ====
%% <ul>
%% <li>?D_(Domain, Text, Locale)</li>
%% <li>?DN_(Domain, Singular, Plural, N, Locale)</li>
%% <li>?DP_(Domain, Context, Text, Locale)</li>
%% <li>?DNP_(Domain, Context, Singular, Plural, N, Locale)</li>
%% </ul>
%% @end
%% Created : 25 Feb 2014 by Sergey Prokhorov <me@seriyps.ru>
-module(gettexter).
%% gettext
-export([gettext/1, gettext/2,
pgettext/2, pgettext/3,
dgettext/2, dgettext/3,
dpgettext/3, dpgettext/4,
ngettext/3, ngettext/4,
npgettext/4, npgettext/5,
dngettext/4, dngettext/5,
dnpgettext/5, dnpgettext/6]).
%% configuration
-export([bindtextdomain/2]).
-export([setlocale/2, getlocale/1]).
-export([textdomain/0, textdomain/1]).
-export([bind_textdomain_codeset/2]).
%% auxillary
-export([which_domains/1, which_locales/1, ensure_loaded/3, reset/0]).
%% helper types
-type text() :: binary() | string().
-type locale() :: atom() | binary() | string().
-export_type([text/0, locale/0]).
%%
%% gettext
%%
%% @doc Translate text.
%% Locale in process dictionary.
-spec gettext(Text :: Type) -> Type when Type :: text().
gettext(Text) -> dpgettext(undefined, undefined, Text).
%% @doc Translate text to a given locale
-spec gettext(Text :: Type, Locale :: locale()) -> Type when Type :: text().
gettext(Text, Locale) -> dpgettext(undefined, undefined, Text, Locale).
%% @doc Translate plural text.
-spec ngettext(Singular :: Type, Plural :: Type, N :: integer()) ->
Type when Type :: text().
ngettext(Singular, Plural, N) ->
dnpgettext(undefined, undefined, Singular, Plural, N).
%% @doc Translate plural text given a locale.
-spec ngettext(Singular :: Type, Plural :: Type, N :: integer(),
Locale :: locale()) -> Type when Type :: text().
ngettext(Singular, Plural, N, Locale) ->
dnpgettext(undefined, undefined, Singular, Plural, N, Locale).
%% @doc Translate a text in a context.
-spec pgettext(Context :: Type | undefined, Text :: Type) ->
Type when Type :: text().
pgettext(Context, Text) -> dpgettext(undefined, Context, Text).
%% @doc Translate a text in a context given a locale.
-spec pgettext(Context :: Type | undefined, Text :: Type, Locale :: locale()) ->
Type when Type :: text().
pgettext(Context, Text, Locale) -> dpgettext(undefined, Context, Text, Locale).
%% @doc Translate a plural text in a context.
-spec npgettext(Context :: Type | undefined, Singular :: Type,
Plural :: Type, N :: integer()) -> Type when Type :: text().
npgettext(Context, Singular, Plural, N) ->
dnpgettext(undefined, Context, Singular, Plural, N).
%% @doc Translate a plural text in a context given a locale.
-spec npgettext(Context :: Type | undefined, Singular :: Type,
Plural :: Type, N :: integer(), Locale :: locale()) ->
Type when Type :: text().
npgettext(Context, Singular, Plural, N, Locale) ->
dnpgettext(undefined, Context, Singular, Plural, N, Locale).
%% @doc Translate a domain-specific text.
-spec dgettext(Domain :: atom(), Text :: Type) -> Type when Type :: text().
dgettext(Domain, Text) -> dpgettext(Domain, undefined, Text).
%% @doc Translate a domain-specific text given a locale.
-spec dgettext(Domain :: atom(), Text :: Type, Locale :: locale()) ->
Type when Type :: text().
dgettext(Domain, Text, Locale) -> dpgettext(Domain, undefined, Text, Locale).
%% @doc Translate a domain-specific plural text.
-spec dngettext(Domain :: atom(), Singular :: Type, Plural :: Type,
N :: integer()) -> Type when Type :: text().
dngettext(Domain, Singular, Plural, N) ->
dnpgettext(Domain, undefined, Singular, Plural, N).
%% @doc Translate a domain-specific plural text given a locale.
-spec dngettext(Domain :: atom(), Singular :: Type, Plural :: Type,
N :: integer(), Locale :: locale()) -> Type when Type :: text().
dngettext(Domain, Singular, Plural, N, Locale) ->
dnpgettext(Domain, undefined, Singular, Plural, N, Locale).
%% @doc Translate a domain-specific text in a context.
-spec dpgettext(Domain :: atom(), Context :: Type | undefined,
Text :: Type) -> Type when Type :: text().
dpgettext(Domain, Context, Text) ->
Locale = getlocale(lc_messages),
dpgettext(Domain, Context, Text, Locale).
%% @doc Translate a domain-specific text in a context given a locale.
-spec dpgettext(Domain :: atom(), Context :: Type | undefined,
Text :: Type, Locale :: locale()) -> Type when Type :: text().
%% binary case
dpgettext(Domain, Context, Text, Locale)
when (Context == undefined orelse is_binary(Context)) andalso is_binary(Text) ->
Domain1 = if Domain == undefined -> textdomain();
true -> Domain
end,
case gettexter_server:dpgettext(Domain1, Context, Text, to_binary(Locale)) of
undefined -> Text;
Translation -> Translation
end;
%% string case
dpgettext(Domain, MaybeContext, StrText, Locale)
when (MaybeContext == undefined orelse is_list(MaybeContext)) andalso is_list(StrText) ->
Context = case MaybeContext of
undefined -> undefined;
StrContext -> unicode:characters_to_binary(StrContext)
end,
Text = unicode:characters_to_binary(StrText),
Translation = dpgettext(Domain, Context, Text, Locale),
unicode:characters_to_list(Translation).
%% @doc Translate a domain-specific plural text in a context.
-spec dnpgettext(Domain :: atom(), Context :: Type | undefined,
Singular :: Type, Plural :: Type, N :: integer()) ->
Type when Type :: text().
dnpgettext(Domain, Context, Singular, Plural, N) ->
Locale = getlocale(lc_messages),
dnpgettext(Domain, Context, Singular, Plural, N, Locale).
%% @doc Translate a domain-specific plural text in a context given a locale.
-spec dnpgettext(Domain :: atom(), Context :: Type | undefined,
Singular :: Type, Plural :: Type, N :: integer(),
Locale :: locale()) -> Type when Type :: text().
%% binary case
dnpgettext(Domain, Context, Singular, Plural, N, Locale)
when (Context == undefined orelse is_binary(Context)) andalso
is_binary(Singular) andalso is_binary(Plural) ->
Domain1 = if Domain == undefined -> textdomain();
true -> Domain
end,
Translation = gettexter_server:dnpgettext(Domain1, Context, Singular, Plural,
N, to_binary(Locale)),
case Translation of
undefined when N == 1 -> Singular;
undefined -> Plural;
Translation -> Translation
end;
%% string case
dnpgettext(Domain, MaybeContext, StrSingular, StrPlural, N, Locale)
when (MaybeContext == undefined orelse is_list(MaybeContext)) andalso
is_list(StrSingular) andalso is_list(StrPlural) ->
Context = case MaybeContext of
undefined -> undefined;
StrContext -> unicode:characters_to_binary(StrContext)
end,
Singular = unicode:characters_to_binary(StrSingular),
Plural = unicode:characters_to_binary(StrPlural),
Translation = dnpgettext(Domain, Context, Singular, Plural, N, Locale),
unicode:characters_to_list(Translation).
%%
%% configuration
%%
%% @doc Bind a domain to a directory.
%% Bind a given domain to a directory where the compiled translated
%% .mo-files are located. Should be called att configuration time,
%% otherwise the default directory priv/locale/ will be used.
-spec bindtextdomain(Domain :: atom(), Localedir :: file:filename()) -> ok.
bindtextdomain(Domain, LocaleDir) ->
gettexter_server:bindtextdomain(Domain, LocaleDir).
%% @doc Set the locale in the process dictionary.
-spec setlocale(Category :: lc_messages, Locale :: locale()) -> ok.
setlocale(Category=lc_messages, Locale) ->
TextDomain = textdomain(),
% assert
true = (TextDomain =/= undefined),
put({?MODULE, locale, TextDomain, Category}, Locale),
{ok, _} = gettexter_server:ensure_loaded(TextDomain, Category, Locale),
ok.
%% @doc Get the locale from the process dictionary.
-spec getlocale(Category :: lc_messages) -> locale() | undefined.
getlocale(Category=lc_messages) ->
TextDomain = textdomain(),
get({?MODULE, locale, TextDomain, Category}).
%% @doc Set the text domain.
-spec textdomain(Domain :: atom()) -> ok.
textdomain(Domain) ->
put({?MODULE, textdomain}, Domain).
%% @doc Get the text domain.
-spec textdomain() -> atom() | undefined.
textdomain() ->
get({?MODULE, textdomain}).
%% @todo
%% @doc TODO
-spec bind_textdomain_codeset(atom(), string()) -> ok.
bind_textdomain_codeset(_Domain, _Charset) ->
error(not_implemented).
%%
%% auxillary
%%
%% @doc Which domains are loaded for `Locale'.
-spec which_domains(Locale :: locale()) -> [atom()].
which_domains(Locale) ->
gettexter_server:which_domains(to_binary(Locale)).
%% @doc Which locales are loaded for `Domain'.
-spec which_locales(Domain :: atom()) -> [locale()].
which_locales(Domain) ->
gettexter_server:which_locales(Domain).
%% @doc Ensure that `Locale' is loaded.
%% If the locale isn't loaded, all lookups will return their own inputs.
-spec ensure_loaded(Domain :: atom(), Category :: lc_messages,
Locale :: locale()) ->
{ok, file:filename() | already} |{error, any()}.
ensure_loaded(Domain, Category, Locale) ->
gettexter_server:ensure_loaded(Domain, Category, to_binary(Locale)).
%% @doc Reset the process dictionary
%% Remove all gettext stuff from process dictionary (but not from locale data
%% storage).
-spec reset() -> ok.
reset() ->
lists:foreach(fun({Key, _}) when element(1, Key) == ?MODULE -> erase(Key);
(_) -> ok
end,
get()),
ok.
to_binary(Bin) when is_binary(Bin) ->
Bin;
to_binary(List) when is_list(List) ->
unicode:characters_to_binary(List);
to_binary(Atom) when is_atom(Atom) ->
atom_to_binary(Atom, unicode).