From c378ea403e7821dff968c185439ce57d77b7b367 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Sun, 24 Sep 2017 00:08:01 +0300 Subject: [PATCH] Add script to extract translation strings --- src/ejabberd_oauth.erl | 2 +- src/mod_muc_log.erl | 91 ++++++++------- src/mod_muc_room.erl | 4 +- src/mod_vcard_ldap.erl | 46 ++++---- src/mod_vcard_mnesia.erl | 48 ++++---- src/mod_vcard_sql.erl | 48 ++++---- src/translate.erl | 6 +- tools/extract-tr.sh | 232 +++++++++++++++++++++++++++++++++++++++ 8 files changed, 356 insertions(+), 121 deletions(-) create mode 100755 tools/extract-tr.sh diff --git a/src/ejabberd_oauth.erl b/src/ejabberd_oauth.erl index 3e3fc3082..def4b225a 100644 --- a/src/ejabberd_oauth.erl +++ b/src/ejabberd_oauth.erl @@ -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">>), diff --git a/src/mod_muc_log.erl b/src/mod_muc_log.erl index 61101d1c2..91203530d 100644 --- a/src/mod_muc_log.erl +++ b/src/mod_muc_log.erl @@ -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 -> <<"
", @@ -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) -> diff --git a/src/mod_muc_room.erl b/src/mod_muc_room.erl index fde43694c..41e776339 100644 --- a/src/mod_muc_room.erl +++ b/src/mod_muc_room.erl @@ -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 diff --git a/src/mod_vcard_ldap.erl b/src/mod_vcard_ldap.erl index 38c4747e6..869f9348b 100644 --- a/src/mod_vcard_ldap.erl +++ b/src/mod_vcard_ldap.erl @@ -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@">>), diff --git a/src/mod_vcard_mnesia.erl b/src/mod_vcard_mnesia.erl index d2f4ef52d..08dd4f940 100644 --- a/src/mod_vcard_mnesia.erl +++ b/src/mod_vcard_mnesia.erl @@ -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}, diff --git a/src/mod_vcard_sql.erl b/src/mod_vcard_sql.erl index fd1d05478..28a6f2ce5 100644 --- a/src/mod_vcard_sql.erl +++ b/src/mod_vcard_sql.erl @@ -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( diff --git a/src/translate.erl b/src/translate.erl index 240a423d6..aaadcd967 100644 --- a/src/translate.erl +++ b/src/translate.erl @@ -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 index 000000000..1a864f6a8 --- /dev/null +++ b/tools/extract-tr.sh @@ -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.