forked from besport/ocsigen-i18n
-
Notifications
You must be signed in to change notification settings - Fork 0
/
i18n_generate.mll
332 lines (299 loc) · 11.6 KB
/
i18n_generate.mll
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
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
(*
* Copyright (C) 2015 BeSport, Julien Sagot
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, with linking exception;
* either version 2.1 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*)
(* Warning: Tsv file need to end with '\r' *)
{
type i18n_expr =
| Var of string (* {{ user }} *)
| Var_typed of string * string (* {{ n %.2f }} *)
| Str of string (* This is a string *)
| Cond of string * string * string (* {{{ many ? s || }}} *)
let flush buffer acc =
let acc = match String.escaped (Buffer.contents buffer) with
| "" -> acc
| x -> Str x :: acc in
Buffer.clear buffer
; acc
}
let lower = ['a'-'z']
let upper = ['A'-'Z']
let num = ['0'-'9']
let id = (lower | ['_']) (lower | upper | num | ['_'])*
rule parse_lines langs acc = parse
| (id as key) '\t' {
(* FIXME: Will break if List.map change its order of execution *)
let tr = List.map (fun lang ->
(lang, parse_expr (Buffer.create 0) [] lexbuf) ) langs in
eol langs ((key, tr) :: acc) lexbuf }
| eof { List.rev acc }
and eol langs acc = parse
| [^'\n']* "\n" { Lexing.new_line lexbuf
; parse_lines langs acc lexbuf}
| eof { List.rev acc }
and parse_expr buffer acc = parse
| "{{{" ' '* (id as c) ' '* "?" {
let s1 = parse_string_1 (Buffer.create 0) lexbuf in
let s2 = parse_string_2 (Buffer.create 0) lexbuf in
let acc = flush buffer acc in
parse_expr buffer (Cond (c, s1, s2) :: acc) lexbuf
}
| "{{" ' '* (id as x) ' '* "}}" {
let acc = flush buffer acc in
parse_expr buffer (Var x :: acc) lexbuf }
| "{{" ' '* (id as x) ' '* ('%' [^ ' ' '}']+ as f) ' '* "}}" {
let acc = flush buffer acc in
parse_expr buffer (Var_typed (x, f) :: acc) lexbuf }
| '\t' | "" { List.rev (flush buffer acc ) }
| [^ '\n' '\t'] as c { Buffer.add_char buffer c
; parse_expr buffer acc lexbuf }
and parse_string_1 buffer = parse
| "||" { String.escaped (Buffer.contents buffer) }
| _ as c { Buffer.add_char buffer c
; parse_string_1 buffer lexbuf }
and parse_string_2 buffer = parse
| "}}}" { String.escaped (Buffer.contents buffer) }
| _ as c { Buffer.add_char buffer c
; parse_string_2 buffer lexbuf }
{
let print_list_of_languages fmt ~variants =
Format.fprintf fmt
"let%%shared languages = [%a]\n"
(Format.pp_print_list
~pp_sep:(fun fmt () -> Format.pp_print_string fmt ";")
Format.pp_print_string) variants
let print_type fmt ~variants =
Format.fprintf fmt
"[%%%%shared type t = %a]\n\
[%%%%shared exception Unknown_language of string]\n"
(Format.pp_print_list
~pp_sep:(fun fmt () -> Format.pp_print_string fmt "|")
Format.pp_print_string) variants
let print_header fmt ?primary_module ~default_language () =
let server_language_reference =
match primary_module with
| None -> "Eliom_reference.Volatile.eref\n\
~scope:Eliom_common.default_process_scope default_language"
| Some module_name -> module_name ^ "._language_"
and client_language_reference =
match primary_module with
| None -> "ref default_language"
| Some module_name -> module_name ^ "._language_"
and default_lang =
match primary_module with
| None -> "let%shared default_language = " ^ default_language ^ "\n"
| Some module_name -> ""
in
Format.pp_print_string fmt @@
default_lang ^
"let%server _language_ = " ^ server_language_reference ^ "\n\
let%server get_language () = Eliom_reference.Volatile.get _language_\n\
let%server set_language language = \n\
Eliom_reference.Volatile.set _language_ language\n\
\n\
let%client _language_ = " ^ client_language_reference ^ "\n\
let%client get_language () = !_language_\n\
let%client set_language language = _language_ := language\n\
\n\
let%shared txt = Eliom_content.Html.F.txt\n\
"
(** Print the function [string_of_language] returning the string representation of a
value o type t. The string representation is simply the value as a string. For
example, the string representation of [Us] is ["Us"]
*)
let print_string_of_language fmt ~variants ~strings =
Format.pp_print_string fmt "let%shared string_of_language = function \n" ;
List.iter2 (fun v s -> Format.fprintf fmt "| %s -> %S" v s)
variants strings ;
Format.pp_print_string fmt "\n"
(** Print the function [language_of_string] returning the value of type t which
corresponds to the given string. The exception [Unknown_language] is raised with
the given string if the language doesn't exist.
*)
let print_language_of_string fmt ~variants ~strings =
Format.pp_print_string fmt "let%shared language_of_string = function\n" ;
List.iter2 (fun v s -> Format.fprintf fmt "| %S -> %s" s v)
variants strings ;
Format.pp_print_string fmt "| s -> raise (Unknown_language s)\n"
let print_guess_language_of_string fmt =
Format.pp_print_string fmt
"let%shared guess_language_of_string s = \n\
try language_of_string s \n\
with Unknown_language _ as e -> \n\
try language_of_string (String.sub s 0 (String.index s '-')) \n\
with Not_found -> \n\
raise e \n"
type arg = M of string | O of string
let print_module_body print_expr =
let args languages =
let rec f a =
function [] -> List.rev a
| Var x :: t -> f (M x :: a) t
| Var_typed (x, _) :: t -> f (M x :: a) t
| Cond (x, _, _) :: t -> f (O x :: a) t
| _ :: t -> f a t in
List.map (f []) languages
|> List.flatten
|> List.sort_uniq compare in
let print_args fmt args =
Format.pp_print_list
~pp_sep:(fun fmt () -> Format.pp_print_char fmt ' ')
(fun fmt -> function
| M x -> Format.fprintf fmt "~%s" x
| O x -> Format.fprintf fmt "?(%s=false)" x) fmt args in
Format.pp_print_list
~pp_sep:(fun fmt () -> Format.pp_print_string fmt "\n")
(fun fmt (key, tr) ->
let args = args (List.map snd tr) in
Format.fprintf fmt "let %s ?(lang = get_language ()) () %a () =\n\
match lang with\n%a"
key
print_args args
(Format.pp_print_list
~pp_sep:(fun fmt () -> Format.pp_print_string fmt "\n")
(fun fmt (language, tr) ->
Format.fprintf fmt "| %s -> %a"
language print_expr tr) ) tr )
let pp_print_list fmt printer =
Format.fprintf fmt "[%a]"
(Format.pp_print_list
~pp_sep:(fun fmt () -> Format.pp_print_string fmt ";")
printer)
let print_expr_html fmt key_values =
let print_key_value fmt =
function
| Str s -> Format.fprintf fmt "[txt \"%s\"]" s
| Var v -> Format.pp_print_string fmt v
| Var_typed (v, f) ->
Format.fprintf fmt "[txt (Printf.sprintf \"%s\" %s)]" f v
| Cond (c, s1, s2) ->
Format.fprintf fmt "[txt (if %s then \"%s\" else \"%s\")]"
c s1 s2
in
match key_values with
| [] ->
assert false
| [key_value] ->
print_key_value fmt key_value
| _ ->
Format.fprintf fmt "List.flatten " ;
pp_print_list fmt print_key_value key_values
let print_expr_string fmt key_values =
let print_key_value fmt =
function
| Str s -> Format.fprintf fmt "\"%s\"" s
| Var v -> Format.pp_print_string fmt v
| Var_typed (v, f) ->
Format.fprintf fmt "(Printf.sprintf \"%s\" %s)" f v
| Cond (c, s1, s2) ->
Format.fprintf fmt "(if %s then \"%s\" else \"%s\")"
c s1 s2
in
match key_values with
| [] ->
assert false
| [key_value] ->
print_key_value fmt key_value
| _ ->
Format.fprintf fmt "String.concat \"\" " ;
pp_print_list fmt print_key_value key_values
let input_file = ref "-"
let output_file = ref "-"
let languages = ref ""
let default_language = ref ""
let external_type = ref false
let primary_file = ref ""
let options = Arg.align
[ ( "--languages", Arg.Set_string languages
, " Comma-separated languages (e.g. en,fr-fr, or Foo.Fr,Foo.Us if \
using external types). \
Must be ordered as in source TSV file.")
; ( "--default-language", Arg.Set_string default_language
, " Set the default language (default is the first one in --languages).")
; ( "--input-file", Arg.Set_string input_file
, " TSV file containing keys and translations. \
If option is omited or set to -, read on stdin.")
; ( "--ouput-file", Arg.Set_string output_file
, " File TSV file containing keys and translations. \
If option is omited or set to -, write on stdout.")
; ( "--external-type", Arg.Set external_type
, " Values passed to --languages option come from a predefined type \
(do not generate the type nor from/to string functions).")
; ( "--primary", Arg.Set_string primary_file
, " Generated file is secondary and depends on given primary file.")
]
let usage = "usage: ocsigen-i18n-generator [options] [< input] [> output]"
let _ = Arg.parse options (fun s -> ()) usage
let normalize_type ?primary_module s =
let constr =
String.lowercase_ascii s
|> Str.(global_replace (regexp "-") "_")
|> String.capitalize_ascii
in
match primary_module with
| None -> constr
| Some module_name -> module_name ^ "." ^ constr
let _ =
let in_chan =
match !input_file with
| "-" -> stdin
| file -> open_in file in
let out_chan =
match !output_file with
| "-" -> stdout
| file -> open_out file in
let primary_module = match !primary_file with
| "" -> None
| file -> let base = Filename.remove_extension file in
Some (String.capitalize_ascii base)
in
let strings = Str.split (Str.regexp ",") !languages in
let variants =
if !primary_file = "" || not (!external_type)
then List.map (normalize_type ?primary_module) strings
else strings in
let default_language =
match !default_language with
| "" -> (List.hd variants)
| x ->
let x = normalize_type ?primary_module x in
assert (List.mem x variants) ;
x in
let lexbuf = Lexing.from_channel in_chan in
(try
let key_values = parse_lines variants [] lexbuf in
let output = Format.formatter_of_out_channel out_chan in
if primary_module = None && not (!external_type) then
( print_type output ~variants
; print_string_of_language output ~variants ~strings
; print_language_of_string output ~variants ~strings
; print_guess_language_of_string output) ;
print_list_of_languages output ~variants ;
print_header output ?primary_module ~default_language () ;
Format.pp_print_string output "[%%shared\n" ;
Format.fprintf output "module Tr = struct\n" ;
print_module_body print_expr_html output key_values ;
Format.fprintf output "\nmodule S = struct\n" ;
print_module_body print_expr_string output key_values ;
Format.fprintf output "\nend\n" ;
Format.fprintf output "end\n" ;
Format.pp_print_string output "]\n"
with Failure msg ->
failwith (Printf.sprintf "line: %d"
lexbuf.Lexing.lex_curr_p.Lexing.pos_lnum) ) ;
close_in in_chan ;
close_out out_chan
}