Skip to content

Instantly share code, notes, and snippets.

@seriyps
Last active August 29, 2015 14:01
Show Gist options
  • Save seriyps/671bba33eb45915ede47 to your computer and use it in GitHub Desktop.
Save seriyps/671bba33eb45915ede47 to your computer and use it in GitHub Desktop.
.po file generator from erlydtl templates
%% Usage:
%% TemplatesDir = "templates",
%% TemplatesNames = ["index.dtl", "_layout.dtl"], % filelib:wildcard("*.dtl", TemplatesDir)
%% LocalesDir = "locales",
%% Domain = "my_project", % phrases will be written to "locales/my_project.pot"
%% Messages = extract_messages(TemplatesDir, TemplatesNames),
%% write_pot(Messages, LocalesDir, Domain).
-module(erlydtl_xgettext).
-export([extract_messages/2, write_pot/3]).
-spec extract_messages(string(), [string()]) -> [{Key, Info | [Info]}]
when
Key :: {Msgid::string(),
MsgidPlural::string() | undefined,
Msgctx::string() | undefined},
Info :: sources_parser:phrase().
extract_messages(PpDir, TplFnames) ->
Msgs = sources_parser:parse_pattern([filename:join(PpDir, N) || N <- TplFnames]),
Msgs1 = merge_messages(Msgs),
lists:sort(fun({_, A}, {_, B}) -> sort_key(A) =< sort_key(B) end, Msgs1).
%% Group messages by key
merge_messages(Msgs) ->
Msgs1 = lists:map(fun (Msg) -> {merge_key(Msg), Msg} end, Msgs),
Msgs2 = lists:keysort(1, Msgs1),
merge_messages(Msgs2, []).
merge_messages([{Key, Info} | In], [{Key, Info2} | Out]) when is_tuple(Info2) ->
merge_messages(In, [{Key, [Info, Info2]} | Out]);
merge_messages([{Key, Info} | In], [{Key, Info2} | Out]) when is_list(Info2) ->
merge_messages(In, [{Key, [Info | Info2]} | Out]);
merge_messages([Pair | In], Out) ->
merge_messages(In, [Pair | Out]);
merge_messages([], Out) ->
Out.
merge_key(Phrase) ->
sources_parser:phrase_info([msgid, msgid_plural, context], Phrase).
sort_key([Phrase | _]) ->
sort_key(Phrase);
sort_key(Phrase) ->
sources_parser:phrase_info([file, line, col], Phrase).
write_pot(Messages, LocalesDir, Domain) ->
Fname = filename:join(LocalesDir, Domain ++ ".pot"),
filelib:ensure_dir(Fname),
{ok,Fd} = file:open(Fname, [write]),
write_header(Fd),
write_entries(Messages, Fd),
file:close(Fd),
Fname.
%% from gettext_compile.erl
write_header(Fd) ->
H = "# SOME DESCRIPTIVE TITLE.\n"
"# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER\n"
"# This file is distributed under the same license as the "
"PACKAGE package.\n"
"# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\n"
"#\n"
"# NB: Consider using poEdit <http://poedit.sourceforge.net>\n"
"#\n"
"#\n"
"#, fuzzy\n"
"msgid \"\"\n"
"msgstr \"\"\n"
"\"Project-Id-Version: PACKAGE VERSION\\n\"\n"
"\"POT-Creation-Date: 2013-08-21 16:45+0200\\n\"\n"
"\"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n\"\n"
"\"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n\"\n"
"\"Language-Team: LANGUAGE <LL@li.org>\\n\"\n"
"\"Language: LC\\n\"\n"
"\"MIME-Version: 1.0\\n\"\n"
"\"Content-Type: text/plain; charset=utf-8\\n\"\n"
"\"Content-Transfer-Encoding: 8bit\\n\"\n",
file:write(Fd, H).
write_entries(L, Fd) ->
F = fun({[Msgid, MsgidPlural, Context], Phrases}) ->
Ct = fmt_comment(Phrases),
file:write(Fd, Ct),
Fi = fmt_location(Phrases),
file:write(Fd, Fi),
file:write(Fd, "\nmsgid "),
gettext_compile:write_pretty(Msgid, Fd),
case MsgidPlural of
undefined -> ok;
_ ->
file:write(Fd, "\nmsgid_plural "),
gettext_compile:write_pretty(MsgidPlural, Fd)
end,
case Context of
undefined -> ok;
_ ->
file:write(Fd, "\nmsgctx "),
gettext_compile:write_pretty(Context, Fd)
end,
file:write(Fd, "msgstr \"\"")%% ,
%% gettext_compile:write_pretty("", Fd)
end,
lists:foreach(F, L).
fmt_comment(Infos) when is_list(Infos) ->
[fmt_comment(I) || I <- Infos];
fmt_comment(Phrase) ->
case sources_parser:phrase_info(comment, Phrase) of
undefined -> [];
Comment ->
["\n#. ", Comment]
end.
fmt_location(Infos) when is_list(Infos) ->
[fmt_location(I) || I <- Infos];
fmt_location(Phrase) ->
[Fname, Line, Col] = sources_parser:phrase_info([file, line, col], Phrase),
["\n#: ", Fname, ":", to_list(Line), ":", to_list(Col)].
to_list(A) when is_atom(A) -> atom_to_list(A);
to_list(I) when is_integer(I) -> integer_to_list(I);
to_list(B) when is_binary(B) -> binary_to_list(B);
to_list(L) when is_list(L) -> L.
%% coding: utf-8
%% -------------------------------------------------------------------------
%% Permission is hereby granted, free of charge, to any person obtaining a
%% copy of this software and associated documentation files (the
%% "Software"), to deal in the Software without restriction, including
%% without limitation the rights to use, copy, modify, merge, publish,
%% distribute, sublicense, and/or sell copies of the Software, and to permit
%% persons to whom the Software is furnished to do so, subject to the
%% following conditions:
%%
%% The above copyright notice and this permission notice shall be included
%% in all copies or substantial portions of the Software.
%%
%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
%% OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
%% MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
%% NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
%% DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
%% OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
%% USE OR OTHER DEALINGS IN THE SOFTWARE.
%%
%% @copyright 2003 Torbj?rn T?rnkvist
%% @author Torbj?rn T?rnkvist <tobbe@tornkvist.org>
%% @doc Parse transform for gettext (see gettext.hrl) and conversion
%% from `epot'- to `po'-files.
% this one extracted from https://github.com/etnt/gettext/blob/master/src/gettext_compile.erl
-module(gettext_compile).
-export([write_pretty/2]).
-define(ENDCOL, 72).
-define(PIVOT, 4).
-define(SEP, $\s).
%% Exported for use by POlish.
write_pretty([], _) ->
true;
write_pretty(Str, Fd) when length(Str) =< ?ENDCOL ->
write_string(Str, Fd);
write_pretty(Str, Fd) ->
{Line, Rest} = get_line(Str),
write_string(Line, Fd),
write_pretty(Rest, Fd).
write_string(Str, Fd) ->
file:write(Fd, "\""),
file:write(Fd, escape_chars(Str)),
file:write(Fd, "\"\n").
escape_chars(Str) ->
F = fun($", Acc) -> [$\\,$"|Acc];
($\\, Acc) -> [$\\,$\\|Acc];
($\n, Acc) -> [$\\,$n|Acc];
(C, Acc) -> [C|Acc]
end,
lists:foldr(F, [], Str).
%%% Split the string into substrings,
%%% aligned around a specific column.
get_line(Str) ->
get_line(Str, ?SEP, 1, ?ENDCOL, []).
%%% End of string reached.
get_line([], _Sep, _N, _End, Acc) ->
{lists:reverse(Acc), []};
%%% Eat characters.
get_line([H|T], Sep, N, End, Acc) when N < End ->
get_line(T, Sep, N+1, End, [H|Acc]);
%%% Ended with a Separator on the End boundary.
get_line([Sep|T], Sep, End, End, Acc) ->
{lists:reverse([Sep|Acc]), T};
%%% At the end, try to find end of token within
%%% the given constraint, else backup one token.
get_line([H|T] = In, Sep, End, End, Acc) ->
case find_end(T, Sep) of
{true, Racc, Rest} ->
{lists:reverse(Racc ++ [H|Acc]), Rest};
false ->
case reverse_tape(Acc, In) of
{true, Bacc, Rest} ->
{lists:reverse(Bacc), Rest};
{false,Str} ->
%%% Ugh...the word is longer than ENDCOL...
split_string(Str, ?ENDCOL)
end
end.
find_end(Str, Sep) ->
find_end(Str, Sep, 1, ?PIVOT, []).
find_end([Sep|T], Sep, N, Pivot, Acc) when N =< Pivot ->
{true, [Sep|Acc], T};
find_end(_Str, _Sep, N, Pivot, _Acc) when N > Pivot ->
false;
find_end([H|T], Sep, N, Pivot, Acc) ->
find_end(T, Sep, N+1, Pivot, [H|Acc]);
find_end([], _Sep, _N, _Pivot, Acc) ->
{true, Acc, []}.
reverse_tape(Acc, Str) ->
reverse_tape(Acc, Str, ?SEP).
reverse_tape([Sep|_T] = In, Str, Sep) ->
{true, In, Str};
reverse_tape([H|T], Str, Sep) ->
reverse_tape(T, [H|Str], Sep);
reverse_tape([], Str, _Sep) ->
{false, Str}.
split_string(Str, End) ->
split_string(Str, End, 1, []).
split_string(Str, End, End, Acc) ->
{lists:reverse(Acc), Str};
split_string([H|T], End, N, Acc) when N < End ->
split_string(T, End, N+1, [H|Acc]);
split_string([], _End, _N, Acc) ->
{lists:reverse(Acc), []}.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment