Source file gettextPo.ml
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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
(** @author Sylvain Le Gall *)
open GettextTypes
(** empty_po : value representing an empty PO *)
let empty_po = GettextPo_utils.empty_po
(** add_po_translation_no_domain po (comment_lst,location_lst,translation) : add
a translation to a corpus of already defined translation with no domain
defined. If the translation already exist, they are merged concerning
location, and follow these rules for the translation itself :
- singular and singular : if there is an empty string ( "" ) in one of the
translation, use the other translation,
- plural and plural : if there is an empty string list ( [ "" ; "" ] ) in
one of the translaiton, use the other translation,
- singular and plural : merge into a plural form. There is checks during the
merge that can raise PoInconsistentMerge :
- for one singular string if the two plural strings differs
- if there is some elements that differs (considering the special case of
the empty string ) in the translation *)
let add_po_translation_no_domain po po_translation =
try GettextPo_utils.add_po_translation_no_domain po po_translation
with PoInconsistentMerge (str1, str2) ->
raise (PoInconsistentMerge (str1, str2))
(** add_po_translation_domain po domain (comment_lst,location_lst,translation):
add a translation to the already defined translation with the domain
defined. See add_translation_no_domain for details. *)
let add_po_translation_domain po domain po_translation =
try GettextPo_utils.add_po_translation_domain po domain po_translation
with PoInconsistentMerge (str1, str2) ->
raise (PoInconsistentMerge (str1, str2))
(** merge_po po1 po2 : merge two PO. The rule for merging are the same as
defined in add_po_translation_no_domain. Can raise PoInconsistentMerge *)
let merge_po po1 po2 =
let merge_no_domain =
MapString.fold
(fun _ translation po -> add_po_translation_no_domain po translation)
po1.no_domain po2
in
let merge_one_domain domain map_domain po =
MapString.fold
(fun _ translation po -> add_po_translation_domain domain po translation)
map_domain po
in
MapTextdomain.fold merge_one_domain po1.domain merge_no_domain
(** merge_pot po pot : merge a PO with a POT. Only consider strings that exists
in the pot. Always use location as defined in the POT. If a string is not
found, use the translation provided in the POT. If a plural is found and a
singular should be used, downgrade the plural to singular. If a singular is
found and a plural should be used, upgrade singular to plural, using the
strings provided in the POT for ending the translation. *)
let merge_pot pot po =
let order_po_map ?domain () =
match domain with
| None ->
po.no_domain
:: MapTextdomain.fold (fun _ x lst -> x :: lst) po.domain []
| Some domain -> (
let tl =
po.no_domain
:: MapTextdomain.fold
(fun key x lst -> if key = domain then lst else x :: lst)
po.domain []
in
try MapTextdomain.find domain po.domain :: tl with Not_found -> tl)
in
let merge_translation map_lst key =
let translation_pot = commented_translation_pot.po_comment_translation in
let translation_merged =
try
let =
let map_po = List.find (MapString.mem key) map_lst in
MapString.find key map_po
in
let translation_po = commented_translation_po.po_comment_translation in
match (translation_pot, translation_po) with
| PoSingular (str_id, _), PoPlural (_, _, str :: _) ->
PoSingular (str_id, str)
| PoPlural (str_id, str_plural, _ :: tl), PoSingular (_, str) ->
PoPlural (str_id, str_plural, str :: tl)
| PoPlural (str_id, str_plural, []), PoSingular (_, str) ->
PoPlural (str_id, str_plural, [ str ])
| _, translation -> translation
with Not_found ->
translation_pot
in
{
commented_translation_pot with
po_comment_translation = translation_merged;
}
in
let merge_no_domain =
MapString.fold
(fun key pot_translation po ->
add_po_translation_no_domain po
(merge_translation (order_po_map ()) key pot_translation))
pot.no_domain empty_po
in
let merge_one_domain domain map_domain po =
MapString.fold
(fun key pot_translation po ->
add_po_translation_domain domain po
(merge_translation (order_po_map ~domain ()) key pot_translation))
map_domain po
in
MapTextdomain.fold merge_one_domain pot.domain merge_no_domain
let input_po chn =
let lexbuf = Lexing.from_channel chn in
try GettextPo_parser.msgfmt GettextPo_lexer.token lexbuf with
| Parsing.Parse_error -> raise (PoInvalidFile ("parse error", lexbuf, chn))
| Failure s -> raise (PoInvalidFile (s, lexbuf, chn))
| PoInconsistentMerge (str1, str2) -> raise (PoInconsistentMerge (str1, str2))
let output_po chn po =
let () = set_binary_mode_out chn true in
let = 80 in
let fpf x = Printf.fprintf chn x in
let escape_string str =
let rec escape_string_aux buff i =
if i < String.length str then
let () =
match str.[i] with
| '\n' -> Buffer.add_string buff "\\n"
| '\t' -> Buffer.add_string buff "\\t"
| '\b' -> Buffer.add_string buff "\\b"
| '\r' -> Buffer.add_string buff "\\r"
| '\012' -> Buffer.add_string buff "\\f"
| '\011' -> Buffer.add_string buff "\\v"
| '\007' -> Buffer.add_string buff "\\a"
| '"' -> Buffer.add_string buff "\\\""
| '\\' -> Buffer.add_string buff "\\\\"
| e -> Buffer.add_char buff e
in
escape_string_aux buff (i + 1)
else ()
in
let buff = Buffer.create (String.length str + 2) in
Buffer.add_char buff '"';
escape_string_aux buff 0;
Buffer.add_char buff '"';
Buffer.contents buff
in
let hyphens chn lst =
match lst with
| [] -> ()
| lst ->
Printf.fprintf chn "%s"
(String.concat "\n" (List.map escape_string lst))
in
let str_hyphen str_sep line_max_length token_lst =
let str_len =
List.fold_left (fun acc str -> acc + String.length str) 0 token_lst
+ (List.length token_lst * String.length str_sep)
in
let buff =
Buffer.create
(str_len + (String.length str_hyphen * (str_len / line_max_length)))
in
let rec first_token line_length lst =
match lst with
| str :: tl ->
let sep_length =
if first_token then 0
else if String.length str + line_length > line_max_length then (
Buffer.add_char buff '\n';
Buffer.add_string buff str_hyphen;
Buffer.add_string buff str_sep;
String.length str_hyphen + String.length str_sep)
else (
Buffer.add_string buff str_sep;
String.length str_sep)
in
Buffer.add_string buff str;
comment_line_aux false
(sep_length + String.length str + line_length)
tl
| [] -> Buffer.contents buff
in
comment_line_aux true 0 token_lst
in
let output_po_translation_aux _ =
(match commented_translation.po_comment_filepos with
| [] -> ()
| lst ->
fpf "%s\n"
(comment_line "#." " " comment_max_length
("#:"
:: List.map
(fun (str, line) -> Printf.sprintf "%s:%d" str line)
lst)));
(match commented_translation.po_comment_special with
| [] -> ()
| lst -> fpf "%s\n" (comment_line "#." " " comment_max_length ("#," :: lst)));
(match commented_translation.po_comment_translation with
| PoSingular (id, str) ->
fpf "msgid %a\n" hyphens id;
fpf "msgstr %a\n" hyphens str
| PoPlural (id, id_plural, lst) ->
fpf "msgid %a\n" hyphens id;
fpf "msgid_plural %a\n" hyphens id_plural;
let _ =
List.fold_left
(fun i s ->
fpf "msgstr[%i] %a\n" i hyphens s;
i + 1)
0 lst
in
());
fpf "\n"
in
MapString.iter output_po_translation_aux po.no_domain;
MapTextdomain.iter
(fun domain map ->
fpf "domain %S\n\n" domain;
MapString.iter output_po_translation_aux map)
po.domain
let translation_of_po_translation po_translation =
match po_translation with
| PoSingular (id, str) -> Singular (String.concat "" id, String.concat "" str)
| PoPlural (id, id_plural, lst) ->
Plural
( String.concat "" id,
String.concat "" id_plural,
List.map (String.concat "") lst )