#!/usr/bin/env escript %% -*- erlang -*- main(Paths) -> Dict = fold_erls( fun(File, Tokens, Acc) -> extract_tr(File, Tokens, Acc) end, dict:new(), Paths), generate_pot(Dict). extract_tr(File, [{'?', _}, {var, _, 'T'}, {'(', Line}|Tokens], Acc) -> case extract_string(Tokens, "") of {"", Tokens1} -> err("~s:~B: Warning: invalid string", [File, Line]), extract_tr(File, Tokens1, Acc); {String, Tokens1} -> extract_tr(File, Tokens1, dict:append(String, {File, Line}, Acc)) end; extract_tr(_File, [{atom,_,module}, {'(',_}, {atom,_,ejabberd_doc} | _Tokens], Acc) -> Acc; extract_tr(File, [{atom, _, F}, {'(',_} | Tokens], Acc) when (F == mod_doc); (F == doc) -> Tokens2 = consume_tokens_until_dot(Tokens), extract_tr(File, Tokens2, Acc); extract_tr(File, [_|Tokens], Acc) -> %%err("~p~n", [A]), extract_tr(File, Tokens, Acc); extract_tr(_, [], Acc) -> Acc. consume_tokens_until_dot([{dot, _} | Tokens]) -> Tokens; consume_tokens_until_dot([_ | Tokens]) -> consume_tokens_until_dot(Tokens). extract_string([{string, _, S}|Tokens], Acc) -> extract_string(Tokens, [S|Acc]); extract_string([{')', _}|Tokens], Acc) -> {lists:flatten(lists:reverse(Acc)), Tokens}; extract_string(Tokens, _) -> {"", Tokens}. fold_erls(Fun, State, Paths) -> Paths1 = fold_paths(Paths), Total = length(Paths1), {_, State1} = lists:foldl( fun(File, {I, Acc}) -> io:format(standard_error, "Progress: ~B% (~B/~B)\r", [round(I*100/Total), I, Total]), case tokens(File) of {ok, Tokens} -> {I+1, Fun(File, Tokens, Acc)}; error -> {I+1, Acc} end end, {0, State}, Paths1), State1. fold_paths(Paths) -> lists:flatmap( fun(Path) -> case filelib:is_dir(Path) of true -> lists:reverse( filelib:fold_files( Path, ".+\.erl\$", false, fun(File, Acc) -> [File|Acc] end, [])); false -> [Path] end end, Paths). tokens(File) -> case file:read_file(File) of {ok, Data} -> case erl_scan:string(binary_to_list(Data)) of {ok, Tokens, _} -> {ok, Tokens}; {error, {_, Module, Desc}, Line} -> err("~s:~n: Warning: scan error: ~s", [filename:basename(File), Line, Module:format_error(Desc)]), error end; {error, Why} -> err("Warning: failed to read file ~s: ~s", [File, file:format_error(Why)]), error end. generate_pot(Dict) -> io:format("~s~n~n", [pot_header()]), lists:foreach( fun({Msg, Location}) -> S1 = format_location(Location), S2 = format_msg(Msg), io:format("~smsgstr \"\"~n~n", [S1 ++ S2]) end, lists:keysort(1, dict:to_list(Dict))). format_location([A, B, C|T]) -> format_location_list([A,B,C]) ++ format_location(T); format_location([A, B|T]) -> format_location_list([A,B]) ++ format_location(T); format_location([A|T]) -> format_location_list([A]) ++ format_location(T); format_location([]) -> "". format_location_list(L) -> "#: " ++ string:join( lists:map( fun({File, Pos}) -> io_lib:format("~s:~B", [File, Pos]) end, L), " ") ++ io_lib:nl(). format_msg(Bin) -> io_lib:format("msgid \"~s\"~n", [escape(Bin)]). escape(Bin) -> lists:map( fun($") -> "\\\""; (C) -> C end, binary_to_list(iolist_to_binary(Bin))). pot_header() -> string:join( ["msgid \"\"", "msgstr \"\"", "\"Project-Id-Version: 15.11.127\\n\"", "\"X-Language: Language Name\\n\"", "\"Last-Translator: Translator name and contact method\\n\"", "\"MIME-Version: 1.0\\n\"", "\"Content-Type: text/plain; charset=UTF-8\\n\"", "\"Content-Transfer-Encoding: 8bit\\n\"", "\"X-Poedit-Basepath: ../..\\n\"", "\"X-Poedit-SearchPath-0: .\\n\""], io_lib:nl()). err(Format, Args) -> io:format(standard_error, Format ++ io_lib:nl(), Args).