25
1
mirror of https://github.com/processone/ejabberd.git synced 2024-11-24 16:23:40 +01:00

Add script to extract translation strings

This commit is contained in:
Evgeniy Khramtsov 2017-09-24 00:08:01 +03:00
parent 692ccd2e20
commit c378ea403e
8 changed files with 356 additions and 121 deletions

View File

@ -436,7 +436,7 @@ process(_Handlers,
?INPUT(<<"hidden">>, <<"scope">>, Scope), ?INPUT(<<"hidden">>, <<"scope">>, Scope),
?INPUT(<<"hidden">>, <<"state">>, State), ?INPUT(<<"hidden">>, <<"state">>, State),
?BR, ?BR,
?LABEL(<<"ttl">>, [?CT(<<"Token TTL">>), ?CT(<<": ">>)]), ?LABEL(<<"ttl">>, [?CT(<<"Token TTL">>), ?C(<<": ">>)]),
?XAE(<<"select">>, [{<<"name">>, <<"ttl">>}], ?XAE(<<"select">>, [{<<"name">>, <<"ttl">>}],
[ [
?XAC(<<"option">>, [{<<"value">>, <<"3600">>}],<<"1 Hour">>), ?XAC(<<"option">>, [{<<"value">>, <<"3600">>}],<<"1 Hour">>),

View File

@ -974,10 +974,9 @@ roomconfig_to_string(Options, Lang, FileFormat) ->
Os2 = lists:sort(Os1), Os2 = lists:sort(Os1),
Options2 = Title ++ Os2, Options2 = Title ++ Os2,
lists:foldl(fun ({Opt, Val}, R) -> lists:foldl(fun ({Opt, Val}, R) ->
case get_roomconfig_text(Opt) of case get_roomconfig_text(Opt, Lang) of
undefined -> R; undefined -> R;
OptT -> OptText ->
OptText = (?T(OptT)),
R2 = case Val of R2 = case Val of
false -> false ->
<<"<div class=\"rcod\">", <<"<div class=\"rcod\">",
@ -1025,49 +1024,49 @@ roomconfig_to_string(Options, Lang, FileFormat) ->
end, end,
<<"">>, Options2). <<"">>, Options2).
get_roomconfig_text(title) -> <<"Room title">>; get_roomconfig_text(title, Lang) -> ?T(<<"Room title">>);
get_roomconfig_text(persistent) -> get_roomconfig_text(persistent, Lang) ->
<<"Make room persistent">>; ?T(<<"Make room persistent">>);
get_roomconfig_text(public) -> get_roomconfig_text(public, Lang) ->
<<"Make room public searchable">>; ?T(<<"Make room public searchable">>);
get_roomconfig_text(public_list) -> get_roomconfig_text(public_list, Lang) ->
<<"Make participants list public">>; ?T(<<"Make participants list public">>);
get_roomconfig_text(password_protected) -> get_roomconfig_text(password_protected, Lang) ->
<<"Make room password protected">>; ?T(<<"Make room password protected">>);
get_roomconfig_text(password) -> <<"Password">>; get_roomconfig_text(password, Lang) -> ?T(<<"Password">>);
get_roomconfig_text(anonymous) -> get_roomconfig_text(anonymous, Lang) ->
<<"This room is not anonymous">>; ?T(<<"This room is not anonymous">>);
get_roomconfig_text(members_only) -> get_roomconfig_text(members_only, Lang) ->
<<"Make room members-only">>; ?T(<<"Make room members-only">>);
get_roomconfig_text(moderated) -> get_roomconfig_text(moderated, Lang) ->
<<"Make room moderated">>; ?T(<<"Make room moderated">>);
get_roomconfig_text(members_by_default) -> get_roomconfig_text(members_by_default, Lang) ->
<<"Default users as participants">>; ?T(<<"Default users as participants">>);
get_roomconfig_text(allow_change_subj) -> get_roomconfig_text(allow_change_subj, Lang) ->
<<"Allow users to change the subject">>; ?T(<<"Allow users to change the subject">>);
get_roomconfig_text(allow_private_messages) -> get_roomconfig_text(allow_private_messages, Lang) ->
<<"Allow users to send private messages">>; ?T(<<"Allow users to send private messages">>);
get_roomconfig_text(allow_private_messages_from_visitors) -> get_roomconfig_text(allow_private_messages_from_visitors, Lang) ->
<<"Allow visitors to send private messages to">>; ?T(<<"Allow visitors to send private messages to">>);
get_roomconfig_text(allow_query_users) -> get_roomconfig_text(allow_query_users, Lang) ->
<<"Allow users to query other users">>; ?T(<<"Allow users to query other users">>);
get_roomconfig_text(allow_user_invites) -> get_roomconfig_text(allow_user_invites, Lang) ->
<<"Allow users to send invites">>; ?T(<<"Allow users to send invites">>);
get_roomconfig_text(logging) -> <<"Enable logging">>; get_roomconfig_text(logging, Lang) -> ?T(<<"Enable logging">>);
get_roomconfig_text(allow_visitor_nickchange) -> get_roomconfig_text(allow_visitor_nickchange, Lang) ->
<<"Allow visitors to change nickname">>; ?T(<<"Allow visitors to change nickname">>);
get_roomconfig_text(allow_visitor_status) -> get_roomconfig_text(allow_visitor_status, Lang) ->
<<"Allow visitors to send status text in " ?T(<<"Allow visitors to send status text in "
"presence updates">>; "presence updates">>);
get_roomconfig_text(captcha_protected) -> get_roomconfig_text(captcha_protected, Lang) ->
<<"Make room captcha protected">>; ?T(<<"Make room captcha protected">>);
get_roomconfig_text(description) -> get_roomconfig_text(description, Lang) ->
<<"Room description">>; ?T(<<"Room description">>);
%% get_roomconfig_text(subject) -> "Subject"; %% get_roomconfig_text(subject, Lang) -> "Subject";
%% get_roomconfig_text(subject_author) -> "Subject author"; %% get_roomconfig_text(subject_author, Lang) -> "Subject author";
get_roomconfig_text(max_users) -> get_roomconfig_text(max_users, Lang) ->
<<"Maximum Number of Occupants">>; ?T(<<"Maximum Number of Occupants">>);
get_roomconfig_text(_) -> undefined. get_roomconfig_text(_, _) -> undefined.
%% Users = [{JID, Nick, Role}] %% Users = [{JID, Nick, Role}]
roomoccupants_to_string(Users, _FileFormat) -> roomoccupants_to_string(Users, _FileFormat) ->

View File

@ -2697,8 +2697,8 @@ find_changed_items(UJID, UAffiliation, URole,
Nick /= <<"">> -> Nick /= <<"">> ->
case find_jids_by_nick(Nick, StateData) of case find_jids_by_nick(Nick, StateData) of
[] -> [] ->
ErrText = str:format(<<"Nickname ~s does not exist in the room">>, ErrText = {<<"Nickname ~s does not exist in the room">>,
[Nick]), [Nick]},
throw({error, xmpp:err_not_acceptable(ErrText, Lang)}); throw({error, xmpp:err_not_acceptable(ErrText, Lang)});
JIDList -> JIDList ->
JIDList JIDList

View File

@ -324,31 +324,31 @@ default_vcard_map() ->
{<<"PHOTO">>, <<"%s">>, [<<"jpegPhoto">>]}]. {<<"PHOTO">>, <<"%s">>, [<<"jpegPhoto">>]}].
default_search_fields() -> default_search_fields() ->
[{<<"User">>, <<"%u">>}, [{translate:mark(<<"User">>), <<"%u">>},
{<<"Full Name">>, <<"displayName">>}, {translate:mark(<<"Full Name">>), <<"displayName">>},
{<<"Given Name">>, <<"givenName">>}, {translate:mark(<<"Given Name">>), <<"givenName">>},
{<<"Middle Name">>, <<"initials">>}, {translate:mark(<<"Middle Name">>), <<"initials">>},
{<<"Family Name">>, <<"sn">>}, {translate:mark(<<"Family Name">>), <<"sn">>},
{<<"Nickname">>, <<"%u">>}, {translate:mark(<<"Nickname">>), <<"%u">>},
{<<"Birthday">>, <<"birthDay">>}, {translate:mark(<<"Birthday">>), <<"birthDay">>},
{<<"Country">>, <<"c">>}, {translate:mark(<<"Country">>), <<"c">>},
{<<"City">>, <<"l">>}, {translate:mark(<<"City">>), <<"l">>},
{<<"Email">>, <<"mail">>}, {translate:mark(<<"Email">>), <<"mail">>},
{<<"Organization Name">>, <<"o">>}, {translate:mark(<<"Organization Name">>), <<"o">>},
{<<"Organization Unit">>, <<"ou">>}]. {translate:mark(<<"Organization Unit">>), <<"ou">>}].
default_search_reported() -> default_search_reported() ->
[{<<"Full Name">>, <<"FN">>}, [{translate:mark(<<"Full Name">>), <<"FN">>},
{<<"Given Name">>, <<"FIRST">>}, {translate:mark(<<"Given Name">>), <<"FIRST">>},
{<<"Middle Name">>, <<"MIDDLE">>}, {translate:mark(<<"Middle Name">>), <<"MIDDLE">>},
{<<"Family Name">>, <<"LAST">>}, {translate:mark(<<"Family Name">>), <<"LAST">>},
{<<"Nickname">>, <<"NICK">>}, {translate:mark(<<"Nickname">>), <<"NICK">>},
{<<"Birthday">>, <<"BDAY">>}, {translate:mark(<<"Birthday">>), <<"BDAY">>},
{<<"Country">>, <<"CTRY">>}, {translate:mark(<<"Country">>), <<"CTRY">>},
{<<"City">>, <<"LOCALITY">>}, {translate:mark(<<"City">>), <<"LOCALITY">>},
{<<"Email">>, <<"EMAIL">>}, {translate:mark(<<"Email">>), <<"EMAIL">>},
{<<"Organization Name">>, <<"ORGNAME">>}, {translate:mark(<<"Organization Name">>), <<"ORGNAME">>},
{<<"Organization Unit">>, <<"ORGUNIT">>}]. {translate:mark(<<"Organization Unit">>), <<"ORGUNIT">>}].
parse_options(Host, Opts) -> parse_options(Host, Opts) ->
MyHosts = gen_mod:get_opt_hosts(Host, Opts, <<"vjud.@HOST@">>), MyHosts = gen_mod:get_opt_hosts(Host, Opts, <<"vjud.@HOST@">>),

View File

@ -95,32 +95,32 @@ search(LServer, Data, AllowReturnAll, MaxMatch) ->
end. end.
search_fields(_LServer) -> search_fields(_LServer) ->
[{<<"User">>, <<"user">>}, [{translate:mark(<<"User">>), <<"user">>},
{<<"Full Name">>, <<"fn">>}, {translate:mark(<<"Full Name">>), <<"fn">>},
{<<"Name">>, <<"first">>}, {translate:mark(<<"Name">>), <<"first">>},
{<<"Middle Name">>, <<"middle">>}, {translate:mark(<<"Middle Name">>), <<"middle">>},
{<<"Family Name">>, <<"last">>}, {translate:mark(<<"Family Name">>), <<"last">>},
{<<"Nickname">>, <<"nick">>}, {translate:mark(<<"Nickname">>), <<"nick">>},
{<<"Birthday">>, <<"bday">>}, {translate:mark(<<"Birthday">>), <<"bday">>},
{<<"Country">>, <<"ctry">>}, {translate:mark(<<"Country">>), <<"ctry">>},
{<<"City">>, <<"locality">>}, {translate:mark(<<"City">>), <<"locality">>},
{<<"Email">>, <<"email">>}, {translate:mark(<<"Email">>), <<"email">>},
{<<"Organization Name">>, <<"orgname">>}, {translate:mark(<<"Organization Name">>), <<"orgname">>},
{<<"Organization Unit">>, <<"orgunit">>}]. {translate:mark(<<"Organization Unit">>), <<"orgunit">>}].
search_reported(_LServer) -> search_reported(_LServer) ->
[{<<"Jabber ID">>, <<"jid">>}, [{translate:mark(<<"Jabber ID">>), <<"jid">>},
{<<"Full Name">>, <<"fn">>}, {translate:mark(<<"Full Name">>), <<"fn">>},
{<<"Name">>, <<"first">>}, {translate:mark(<<"Name">>), <<"first">>},
{<<"Middle Name">>, <<"middle">>}, {translate:mark(<<"Middle Name">>), <<"middle">>},
{<<"Family Name">>, <<"last">>}, {translate:mark(<<"Family Name">>), <<"last">>},
{<<"Nickname">>, <<"nick">>}, {translate:mark(<<"Nickname">>), <<"nick">>},
{<<"Birthday">>, <<"bday">>}, {translate:mark(<<"Birthday">>), <<"bday">>},
{<<"Country">>, <<"ctry">>}, {translate:mark(<<"Country">>), <<"ctry">>},
{<<"City">>, <<"locality">>}, {translate:mark(<<"City">>), <<"locality">>},
{<<"Email">>, <<"email">>}, {translate:mark(<<"Email">>), <<"email">>},
{<<"Organization Name">>, <<"orgname">>}, {translate:mark(<<"Organization Name">>), <<"orgname">>},
{<<"Organization Unit">>, <<"orgunit">>}]. {translate:mark(<<"Organization Unit">>), <<"orgunit">>}].
remove_user(LUser, LServer) -> remove_user(LUser, LServer) ->
US = {LUser, LServer}, US = {LUser, LServer},

View File

@ -150,32 +150,32 @@ search(LServer, Data, AllowReturnAll, MaxMatch) ->
end. end.
search_fields(_LServer) -> search_fields(_LServer) ->
[{<<"User">>, <<"user">>}, [{translate:mark(<<"User">>), <<"user">>},
{<<"Full Name">>, <<"fn">>}, {translate:mark(<<"Full Name">>), <<"fn">>},
{<<"Name">>, <<"first">>}, {translate:mark(<<"Name">>), <<"first">>},
{<<"Middle Name">>, <<"middle">>}, {translate:mark(<<"Middle Name">>), <<"middle">>},
{<<"Family Name">>, <<"last">>}, {translate:mark(<<"Family Name">>), <<"last">>},
{<<"Nickname">>, <<"nick">>}, {translate:mark(<<"Nickname">>), <<"nick">>},
{<<"Birthday">>, <<"bday">>}, {translate:mark(<<"Birthday">>), <<"bday">>},
{<<"Country">>, <<"ctry">>}, {translate:mark(<<"Country">>), <<"ctry">>},
{<<"City">>, <<"locality">>}, {translate:mark(<<"City">>), <<"locality">>},
{<<"Email">>, <<"email">>}, {translate:mark(<<"Email">>), <<"email">>},
{<<"Organization Name">>, <<"orgname">>}, {translate:mark(<<"Organization Name">>), <<"orgname">>},
{<<"Organization Unit">>, <<"orgunit">>}]. {translate:mark(<<"Organization Unit">>), <<"orgunit">>}].
search_reported(_LServer) -> search_reported(_LServer) ->
[{<<"Jabber ID">>, <<"jid">>}, [{translate:mark(<<"Jabber ID">>), <<"jid">>},
{<<"Full Name">>, <<"fn">>}, {translate:mark(<<"Full Name">>), <<"fn">>},
{<<"Name">>, <<"first">>}, {translate:mark(<<"Name">>), <<"first">>},
{<<"Middle Name">>, <<"middle">>}, {translate:mark(<<"Middle Name">>), <<"middle">>},
{<<"Family Name">>, <<"last">>}, {translate:mark(<<"Family Name">>), <<"last">>},
{<<"Nickname">>, <<"nick">>}, {translate:mark(<<"Nickname">>), <<"nick">>},
{<<"Birthday">>, <<"bday">>}, {translate:mark(<<"Birthday">>), <<"bday">>},
{<<"Country">>, <<"ctry">>}, {translate:mark(<<"Country">>), <<"ctry">>},
{<<"City">>, <<"locality">>}, {translate:mark(<<"City">>), <<"locality">>},
{<<"Email">>, <<"email">>}, {translate:mark(<<"Email">>), <<"email">>},
{<<"Organization Name">>, <<"orgname">>}, {translate:mark(<<"Organization Name">>), <<"orgname">>},
{<<"Organization Unit">>, <<"orgunit">>}]. {translate:mark(<<"Organization Unit">>), <<"orgunit">>}].
remove_user(LUser, LServer) -> remove_user(LUser, LServer) ->
ejabberd_sql:sql_transaction( ejabberd_sql:sql_transaction(

View File

@ -29,7 +29,7 @@
-behaviour(gen_server). -behaviour(gen_server).
-export([start_link/0, reload/0, translate/2]). -export([start_link/0, reload/0, translate/2, mark/1]).
%% gen_server callbacks %% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]). terminate/2, code_change/3]).
@ -219,6 +219,10 @@ translate(Msg) ->
end end
end. end.
-spec mark(binary()) -> binary().
mark(Msg) ->
Msg.
ascii_tolower(B) -> ascii_tolower(B) ->
iolist_to_binary(ascii_tolower_s(binary_to_list(B))). iolist_to_binary(ascii_tolower_s(binary_to_list(B))).

232
tools/extract-tr.sh Executable file
View File

@ -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.