]> granicus.if.org Git - ejabberd/commitdiff
Add script to extract translation strings
authorEvgeniy Khramtsov <ekhramtsov@process-one.net>
Sat, 23 Sep 2017 21:08:01 +0000 (00:08 +0300)
committerEvgeniy Khramtsov <ekhramtsov@process-one.net>
Sat, 23 Sep 2017 21:08:01 +0000 (00:08 +0300)
src/ejabberd_oauth.erl
src/mod_muc_log.erl
src/mod_muc_room.erl
src/mod_vcard_ldap.erl
src/mod_vcard_mnesia.erl
src/mod_vcard_sql.erl
src/translate.erl
tools/extract-tr.sh [new file with mode: 0755]

index 3e3fc3082c2d85b332dce8b84f51c6d43e595a4a..def4b225ac3bac18c9fca6edb4fd8c3937be4565 100644 (file)
@@ -436,7 +436,7 @@ process(_Handlers,
               ?INPUT(<<"hidden">>, <<"scope">>, Scope),
               ?INPUT(<<"hidden">>, <<"state">>, State),
               ?BR,
-              ?LABEL(<<"ttl">>, [?CT(<<"Token TTL">>), ?CT(<<": ">>)]),
+              ?LABEL(<<"ttl">>, [?CT(<<"Token TTL">>), ?C(<<": ">>)]),
               ?XAE(<<"select">>, [{<<"name">>, <<"ttl">>}],
                    [
                    ?XAC(<<"option">>, [{<<"value">>, <<"3600">>}],<<"1 Hour">>),
index 61101d1c2ecaa982a602202eb54adb8fe8ab1cf0..91203530d578348e28b2f69cd8eddc4576488fcb 100644 (file)
@@ -974,10 +974,9 @@ roomconfig_to_string(Options, Lang, FileFormat) ->
     Os2 = lists:sort(Os1),
     Options2 = Title ++ Os2,
     lists:foldl(fun ({Opt, Val}, R) ->
-                       case get_roomconfig_text(Opt) of
+                       case get_roomconfig_text(Opt, Lang) of
                          undefined -> R;
-                         OptT ->
-                             OptText = (?T(OptT)),
+                         OptText ->
                              R2 = case Val of
                                     false ->
                                         <<"<div class=\"rcod\">",
@@ -1025,49 +1024,49 @@ roomconfig_to_string(Options, Lang, FileFormat) ->
                end,
                <<"">>, Options2).
 
-get_roomconfig_text(title) -> <<"Room title">>;
-get_roomconfig_text(persistent) ->
-    <<"Make room persistent">>;
-get_roomconfig_text(public) ->
-    <<"Make room public searchable">>;
-get_roomconfig_text(public_list) ->
-    <<"Make participants list public">>;
-get_roomconfig_text(password_protected) ->
-    <<"Make room password protected">>;
-get_roomconfig_text(password) -> <<"Password">>;
-get_roomconfig_text(anonymous) ->
-    <<"This room is not anonymous">>;
-get_roomconfig_text(members_only) ->
-    <<"Make room members-only">>;
-get_roomconfig_text(moderated) ->
-    <<"Make room moderated">>;
-get_roomconfig_text(members_by_default) ->
-    <<"Default users as participants">>;
-get_roomconfig_text(allow_change_subj) ->
-    <<"Allow users to change the subject">>;
-get_roomconfig_text(allow_private_messages) ->
-    <<"Allow users to send private messages">>;
-get_roomconfig_text(allow_private_messages_from_visitors) ->
-    <<"Allow visitors to send private messages to">>;
-get_roomconfig_text(allow_query_users) ->
-    <<"Allow users to query other users">>;
-get_roomconfig_text(allow_user_invites) ->
-    <<"Allow users to send invites">>;
-get_roomconfig_text(logging) -> <<"Enable logging">>;
-get_roomconfig_text(allow_visitor_nickchange) ->
-    <<"Allow visitors to change nickname">>;
-get_roomconfig_text(allow_visitor_status) ->
-    <<"Allow visitors to send status text in "
-      "presence updates">>;
-get_roomconfig_text(captcha_protected) ->
-    <<"Make room captcha protected">>;
-get_roomconfig_text(description) ->
-    <<"Room description">>;
-%% get_roomconfig_text(subject) ->  "Subject";
-%% get_roomconfig_text(subject_author) ->  "Subject author";
-get_roomconfig_text(max_users) ->
-    <<"Maximum Number of Occupants">>;
-get_roomconfig_text(_) -> undefined.
+get_roomconfig_text(title, Lang) -> ?T(<<"Room title">>);
+get_roomconfig_text(persistent, Lang) ->
+    ?T(<<"Make room persistent">>);
+get_roomconfig_text(public, Lang) ->
+    ?T(<<"Make room public searchable">>);
+get_roomconfig_text(public_list, Lang) ->
+    ?T(<<"Make participants list public">>);
+get_roomconfig_text(password_protected, Lang) ->
+    ?T(<<"Make room password protected">>);
+get_roomconfig_text(password, Lang) -> ?T(<<"Password">>);
+get_roomconfig_text(anonymous, Lang) ->
+    ?T(<<"This room is not anonymous">>);
+get_roomconfig_text(members_only, Lang) ->
+    ?T(<<"Make room members-only">>);
+get_roomconfig_text(moderated, Lang) ->
+    ?T(<<"Make room moderated">>);
+get_roomconfig_text(members_by_default, Lang) ->
+    ?T(<<"Default users as participants">>);
+get_roomconfig_text(allow_change_subj, Lang) ->
+    ?T(<<"Allow users to change the subject">>);
+get_roomconfig_text(allow_private_messages, Lang) ->
+    ?T(<<"Allow users to send private messages">>);
+get_roomconfig_text(allow_private_messages_from_visitors, Lang) ->
+    ?T(<<"Allow visitors to send private messages to">>);
+get_roomconfig_text(allow_query_users, Lang) ->
+    ?T(<<"Allow users to query other users">>);
+get_roomconfig_text(allow_user_invites, Lang) ->
+    ?T(<<"Allow users to send invites">>);
+get_roomconfig_text(logging, Lang) -> ?T(<<"Enable logging">>);
+get_roomconfig_text(allow_visitor_nickchange, Lang) ->
+    ?T(<<"Allow visitors to change nickname">>);
+get_roomconfig_text(allow_visitor_status, Lang) ->
+    ?T(<<"Allow visitors to send status text in "
+      "presence updates">>);
+get_roomconfig_text(captcha_protected, Lang) ->
+    ?T(<<"Make room captcha protected">>);
+get_roomconfig_text(description, Lang) ->
+    ?T(<<"Room description">>);
+%% get_roomconfig_text(subject, Lang) ->  "Subject";
+%% get_roomconfig_text(subject_author, Lang) ->  "Subject author";
+get_roomconfig_text(max_users, Lang) ->
+    ?T(<<"Maximum Number of Occupants">>);
+get_roomconfig_text(_, _) -> undefined.
 
 %% Users = [{JID, Nick, Role}]
 roomoccupants_to_string(Users, _FileFormat) ->
index fde43694c145f5b53b49be764d34d88676f38924..41e776339bb2b57e3b87ba18c63af0d5a783d18f 100644 (file)
@@ -2697,8 +2697,8 @@ find_changed_items(UJID, UAffiliation, URole,
           Nick /= <<"">> ->
                case find_jids_by_nick(Nick, StateData) of
                    [] ->
-                       ErrText = str:format(<<"Nickname ~s does not exist in the room">>,
-                                  [Nick]),
+                       ErrText = {<<"Nickname ~s does not exist in the room">>,
+                                  [Nick]},
                        throw({error, xmpp:err_not_acceptable(ErrText, Lang)});
                    JIDList ->
                        JIDList
index 38c4747e60cc070ed1754b98cee6f9383faf6f47..869f9348bb726abe127a591f75bebbc869749c12 100644 (file)
@@ -324,31 +324,31 @@ default_vcard_map() ->
      {<<"PHOTO">>, <<"%s">>, [<<"jpegPhoto">>]}].
 
 default_search_fields() ->
-    [{<<"User">>, <<"%u">>},
-     {<<"Full Name">>, <<"displayName">>},
-     {<<"Given Name">>, <<"givenName">>},
-     {<<"Middle Name">>, <<"initials">>},
-     {<<"Family Name">>, <<"sn">>},
-     {<<"Nickname">>, <<"%u">>},
-     {<<"Birthday">>, <<"birthDay">>},
-     {<<"Country">>, <<"c">>},
-     {<<"City">>, <<"l">>},
-     {<<"Email">>, <<"mail">>},
-     {<<"Organization Name">>, <<"o">>},
-     {<<"Organization Unit">>, <<"ou">>}].
+    [{translate:mark(<<"User">>), <<"%u">>},
+     {translate:mark(<<"Full Name">>), <<"displayName">>},
+     {translate:mark(<<"Given Name">>), <<"givenName">>},
+     {translate:mark(<<"Middle Name">>), <<"initials">>},
+     {translate:mark(<<"Family Name">>), <<"sn">>},
+     {translate:mark(<<"Nickname">>), <<"%u">>},
+     {translate:mark(<<"Birthday">>), <<"birthDay">>},
+     {translate:mark(<<"Country">>), <<"c">>},
+     {translate:mark(<<"City">>), <<"l">>},
+     {translate:mark(<<"Email">>), <<"mail">>},
+     {translate:mark(<<"Organization Name">>), <<"o">>},
+     {translate:mark(<<"Organization Unit">>), <<"ou">>}].
 
 default_search_reported() ->
-    [{<<"Full Name">>, <<"FN">>},
-     {<<"Given Name">>, <<"FIRST">>},
-     {<<"Middle Name">>, <<"MIDDLE">>},
-     {<<"Family Name">>, <<"LAST">>},
-     {<<"Nickname">>, <<"NICK">>},
-     {<<"Birthday">>, <<"BDAY">>},
-     {<<"Country">>, <<"CTRY">>},
-     {<<"City">>, <<"LOCALITY">>},
-     {<<"Email">>, <<"EMAIL">>},
-     {<<"Organization Name">>, <<"ORGNAME">>},
-     {<<"Organization Unit">>, <<"ORGUNIT">>}].
+    [{translate:mark(<<"Full Name">>), <<"FN">>},
+     {translate:mark(<<"Given Name">>), <<"FIRST">>},
+     {translate:mark(<<"Middle Name">>), <<"MIDDLE">>},
+     {translate:mark(<<"Family Name">>), <<"LAST">>},
+     {translate:mark(<<"Nickname">>), <<"NICK">>},
+     {translate:mark(<<"Birthday">>), <<"BDAY">>},
+     {translate:mark(<<"Country">>), <<"CTRY">>},
+     {translate:mark(<<"City">>), <<"LOCALITY">>},
+     {translate:mark(<<"Email">>), <<"EMAIL">>},
+     {translate:mark(<<"Organization Name">>), <<"ORGNAME">>},
+     {translate:mark(<<"Organization Unit">>), <<"ORGUNIT">>}].
 
 parse_options(Host, Opts) ->
     MyHosts = gen_mod:get_opt_hosts(Host, Opts, <<"vjud.@HOST@">>),
index d2f4ef52d819b076cdac3299c7dbcdf3f00a4272..08dd4f940fc157e6c37c9ae4dd46d9991db1e9a4 100644 (file)
@@ -95,32 +95,32 @@ search(LServer, Data, AllowReturnAll, MaxMatch) ->
     end.
 
 search_fields(_LServer) ->
-    [{<<"User">>, <<"user">>},
-     {<<"Full Name">>, <<"fn">>},
-     {<<"Name">>, <<"first">>},
-     {<<"Middle Name">>, <<"middle">>},
-     {<<"Family Name">>, <<"last">>},
-     {<<"Nickname">>, <<"nick">>},
-     {<<"Birthday">>, <<"bday">>},
-     {<<"Country">>, <<"ctry">>},
-     {<<"City">>, <<"locality">>},
-     {<<"Email">>, <<"email">>},
-     {<<"Organization Name">>, <<"orgname">>},
-     {<<"Organization Unit">>, <<"orgunit">>}].
+    [{translate:mark(<<"User">>), <<"user">>},
+     {translate:mark(<<"Full Name">>), <<"fn">>},
+     {translate:mark(<<"Name">>), <<"first">>},
+     {translate:mark(<<"Middle Name">>), <<"middle">>},
+     {translate:mark(<<"Family Name">>), <<"last">>},
+     {translate:mark(<<"Nickname">>), <<"nick">>},
+     {translate:mark(<<"Birthday">>), <<"bday">>},
+     {translate:mark(<<"Country">>), <<"ctry">>},
+     {translate:mark(<<"City">>), <<"locality">>},
+     {translate:mark(<<"Email">>), <<"email">>},
+     {translate:mark(<<"Organization Name">>), <<"orgname">>},
+     {translate:mark(<<"Organization Unit">>), <<"orgunit">>}].
 
 search_reported(_LServer) ->
-    [{<<"Jabber ID">>, <<"jid">>},
-     {<<"Full Name">>, <<"fn">>},
-     {<<"Name">>, <<"first">>},
-     {<<"Middle Name">>, <<"middle">>},
-     {<<"Family Name">>, <<"last">>},
-     {<<"Nickname">>, <<"nick">>},
-     {<<"Birthday">>, <<"bday">>},
-     {<<"Country">>, <<"ctry">>},
-     {<<"City">>, <<"locality">>},
-     {<<"Email">>, <<"email">>},
-     {<<"Organization Name">>, <<"orgname">>},
-     {<<"Organization Unit">>, <<"orgunit">>}].
+    [{translate:mark(<<"Jabber ID">>), <<"jid">>},
+     {translate:mark(<<"Full Name">>), <<"fn">>},
+     {translate:mark(<<"Name">>), <<"first">>},
+     {translate:mark(<<"Middle Name">>), <<"middle">>},
+     {translate:mark(<<"Family Name">>), <<"last">>},
+     {translate:mark(<<"Nickname">>), <<"nick">>},
+     {translate:mark(<<"Birthday">>), <<"bday">>},
+     {translate:mark(<<"Country">>), <<"ctry">>},
+     {translate:mark(<<"City">>), <<"locality">>},
+     {translate:mark(<<"Email">>), <<"email">>},
+     {translate:mark(<<"Organization Name">>), <<"orgname">>},
+     {translate:mark(<<"Organization Unit">>), <<"orgunit">>}].
 
 remove_user(LUser, LServer) ->
     US = {LUser, LServer},
index fd1d054787059a41adb2ecf613ac9fd134019f76..28a6f2ce5ffbb8443c80ffe3d12122b5ad775ed2 100644 (file)
@@ -150,32 +150,32 @@ search(LServer, Data, AllowReturnAll, MaxMatch) ->
     end.
 
 search_fields(_LServer) ->
-    [{<<"User">>, <<"user">>},
-     {<<"Full Name">>, <<"fn">>},
-     {<<"Name">>, <<"first">>},
-     {<<"Middle Name">>, <<"middle">>},
-     {<<"Family Name">>, <<"last">>},
-     {<<"Nickname">>, <<"nick">>},
-     {<<"Birthday">>, <<"bday">>},
-     {<<"Country">>, <<"ctry">>},
-     {<<"City">>, <<"locality">>},
-     {<<"Email">>, <<"email">>},
-     {<<"Organization Name">>, <<"orgname">>},
-     {<<"Organization Unit">>, <<"orgunit">>}].
+    [{translate:mark(<<"User">>), <<"user">>},
+     {translate:mark(<<"Full Name">>), <<"fn">>},
+     {translate:mark(<<"Name">>), <<"first">>},
+     {translate:mark(<<"Middle Name">>), <<"middle">>},
+     {translate:mark(<<"Family Name">>), <<"last">>},
+     {translate:mark(<<"Nickname">>), <<"nick">>},
+     {translate:mark(<<"Birthday">>), <<"bday">>},
+     {translate:mark(<<"Country">>), <<"ctry">>},
+     {translate:mark(<<"City">>), <<"locality">>},
+     {translate:mark(<<"Email">>), <<"email">>},
+     {translate:mark(<<"Organization Name">>), <<"orgname">>},
+     {translate:mark(<<"Organization Unit">>), <<"orgunit">>}].
 
 search_reported(_LServer) ->
-    [{<<"Jabber ID">>, <<"jid">>},
-     {<<"Full Name">>, <<"fn">>},
-     {<<"Name">>, <<"first">>},
-     {<<"Middle Name">>, <<"middle">>},
-     {<<"Family Name">>, <<"last">>},
-     {<<"Nickname">>, <<"nick">>},
-     {<<"Birthday">>, <<"bday">>},
-     {<<"Country">>, <<"ctry">>},
-     {<<"City">>, <<"locality">>},
-     {<<"Email">>, <<"email">>},
-     {<<"Organization Name">>, <<"orgname">>},
-     {<<"Organization Unit">>, <<"orgunit">>}].
+    [{translate:mark(<<"Jabber ID">>), <<"jid">>},
+     {translate:mark(<<"Full Name">>), <<"fn">>},
+     {translate:mark(<<"Name">>), <<"first">>},
+     {translate:mark(<<"Middle Name">>), <<"middle">>},
+     {translate:mark(<<"Family Name">>), <<"last">>},
+     {translate:mark(<<"Nickname">>), <<"nick">>},
+     {translate:mark(<<"Birthday">>), <<"bday">>},
+     {translate:mark(<<"Country">>), <<"ctry">>},
+     {translate:mark(<<"City">>), <<"locality">>},
+     {translate:mark(<<"Email">>), <<"email">>},
+     {translate:mark(<<"Organization Name">>), <<"orgname">>},
+     {translate:mark(<<"Organization Unit">>), <<"orgunit">>}].
 
 remove_user(LUser, LServer) ->
     ejabberd_sql:sql_transaction(
index 240a423d63811a6d12d9170e61ff080231e8793b..aaadcd96738760e367300cd0a653b6572efb5222 100644 (file)
@@ -29,7 +29,7 @@
 
 -behaviour(gen_server).
 
--export([start_link/0, reload/0, translate/2]).
+-export([start_link/0, reload/0, translate/2, mark/1]).
 %% gen_server callbacks
 -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
         terminate/2, code_change/3]).
@@ -219,6 +219,10 @@ translate(Msg) ->
          end
     end.
 
+-spec mark(binary()) -> binary().
+mark(Msg) ->
+    Msg.
+
 ascii_tolower(B) ->
     iolist_to_binary(ascii_tolower_s(binary_to_list(B))).
 
diff --git a/tools/extract-tr.sh b/tools/extract-tr.sh
new file mode 100755 (executable)
index 0000000..1a864f6
--- /dev/null
@@ -0,0 +1,232 @@
+#!/usr/bin/env escript
+%% -*- erlang -*-
+%%! -pa ebin
+
+main([Dir]) ->
+    Txts =
+       filelib:fold_files(
+         Dir, ".+\.beam\$", false,
+         fun(FileIn, Res) ->
+                 case get_forms(FileIn) of
+                     {ok, Forms} ->
+                         Tree = erl_syntax:form_list(Forms),
+                         Mod = mod(FileIn),
+                         erl_syntax_lib:fold_subtrees(
+                           fun(Form, Acc) ->
+                                   case erl_syntax:type(Form) of
+                                       function ->
+                                           case map(Form, Mod) of
+                                               [] ->
+                                                   Acc;
+                                               Vars ->
+                                                   Vars ++ Acc
+                                           end;
+                                       _ ->
+                                           Acc
+                                   end
+                           end, [], Tree) ++ Res;
+                     _Err ->
+                         Res
+                 end
+         end, []),
+    Dict = lists:foldl(
+            fun({B, Meta}, Acc) ->
+                    dict:update(
+                      binary_to_list(B),
+                      fun(OldMeta) ->
+                              lists:usort([Meta|OldMeta])
+                      end,
+                      [Meta], Acc)
+            end, dict:new(), Txts),
+    generate_pot(Dict).
+
+map(Tree, Mod) ->
+    Vars = erl_syntax_lib:fold(
+            fun(Form, Acc) ->
+                    case erl_syntax:type(Form) of
+                        application ->
+                            analyze_app(Form, Mod) ++ Acc;
+                        _ ->
+                            Acc
+                    end
+            end, [], Tree),
+    Bins = lists:flatmap(
+            fun({Var, Pos}) when is_atom(Var) ->
+                    Res = erl_syntax_lib:fold(
+                            fun(Form, Acc) ->
+                                    case process_match_expr(
+                                           Form, Var, Mod) of
+                                        {ok, Binary, NewPos} ->
+                                            [{Binary, NewPos}|Acc];
+                                        error ->
+                                            Acc
+                                    end
+                            end, [], Tree),
+                    case Res of
+                        [] ->
+                            log("~s:~p: unresolved variable: ~s~n",
+                                [Mod, Pos, Var]);
+                        _ ->
+                            ok
+                    end,
+                    Res;
+               ({Var, Pos}) when is_binary(Var) ->
+                    [{Var, Pos}]
+            end, lists:usort(Vars)),
+    [{B, {Mod, Pos}} || {B, Pos} <- Bins, B /= <<"">>].
+
+process_match_expr(Form, Var, Mod) ->
+    case erl_syntax:type(Form) of
+       match_expr ->
+           Pattern = erl_syntax:match_expr_pattern(Form),
+           Body = erl_syntax:match_expr_body(Form),
+           {V, Expr} =
+               case {erl_syntax:type(Pattern), erl_syntax:type(Body)} of
+                   {variable, _} ->
+                       {erl_syntax:variable_name(Pattern), Body};
+                   {_, variable} ->
+                       {erl_syntax:variable_name(Body), Pattern};
+                   _ ->
+                       {'', none}
+               end,
+           Text = maybe_extract_tuple(Expr),
+           if V == Var ->
+                   Pos = erl_syntax:get_pos(Text),
+                   try {ok, erl_syntax:concrete(Text), Pos}
+                   catch _:_ ->
+                           case catch erl_syntax_lib:analyze_application(Text) of
+                               {_M, {Fn, 1}} when Fn == format_error;
+                                                  Fn == io_format_error ->
+                                   error;
+                               _ ->
+                                   log("~s:~p: not a binary: ~s~n",
+                                       [Mod, Pos, erl_prettypr:format(Text)]),
+                                   {ok, <<>>, Pos}
+                           end
+                   end;
+              true ->
+                   error
+           end;
+       _ ->
+           error
+    end.
+
+maybe_extract_tuple(none) ->
+    none;
+maybe_extract_tuple(Form) ->
+    try
+       tuple = erl_syntax:type(Form),
+       [Text, _] = erl_syntax:tuple_elements(Form),
+       Text
+    catch _:{badmatch, _} ->
+           Form
+    end.
+
+analyze_app(Form, Mod) ->
+    try
+       {M, {F, A}} = erl_syntax_lib:analyze_application(Form),
+       Args = erl_syntax:application_arguments(Form),
+       Txt = case {M, atom_to_list(F), A, Args} of
+                 {xmpp, "err_" ++ _, 2, [T|_]} -> T;
+                 {xmpp, "serr_" ++ _, 2, [T|_]} -> T;
+                 {xmpp, "mk_text", 2, [T|_]} -> T;
+                 {translate, "translate", 2, [_,T|_]} -> T;
+                 {translate, "mark", 1, [T]} -> T
+             end,
+       Pos = erl_syntax:get_pos(Txt),
+       case erl_syntax:type(Txt) of
+           binary ->
+               try [{erl_syntax:concrete(Txt), Pos}]
+               catch _:_ ->
+                       Pos = erl_syntax:get_pos(Txt),
+                       log("~s:~p: not a binary: ~s~n",
+                           [Mod, Pos, erl_prettypr:format(Txt)]),
+                       []
+               end;
+           variable ->
+               [{erl_syntax:variable_name(Txt), Pos}];
+           application ->
+               Vars = sets:to_list(erl_syntax_lib:variables(Txt)),
+               case Vars of
+                   [Var] ->
+                       [{Var, Pos}];
+                   [_|_] ->
+                       log("Too many variables: ~p~n", [Vars]),
+                       [];
+                   [] ->
+                       []
+               end;
+           _ ->
+               []
+       end
+    catch _:{badmatch, _} ->
+           [];
+         _:{case_clause, _} ->
+           []
+    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\""],
+      io_lib:nl()).
+
+mod(Path) ->
+    filename:rootname(filename:basename(Path)) ++ ".erl".
+
+log(Format, Args) ->
+    io:format(standard_error, Format, Args).
+
+get_forms(File) ->
+    case beam_lib:chunks(File, [abstract_code]) of
+        {ok, {_, List}} ->
+            case lists:keyfind(abstract_code, 1, List) of
+                {abstract_code, {raw_abstract_v1, Abstr}} ->
+                    {ok, Abstr};
+                _ ->
+                    error
+            end;
+        _ ->
+            error
+    end.