1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
|
#!/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).
|