Просмотр исходного кода

Support for i18n at run-time.

Pass a translation dictionary as the second argument to "render"
and it will be used to translate strings inside {% trans %} blocks.
Values in this dictionary will override compile-time translations
achieved with the 'locale' compile-time option.

Also generates a new function translatable_strings/0 which returns
a list of {% trans %} strings than can be converted with a dictionary.
Evan Miller 15 лет назад
Родитель
Сommit
550bbe4824

+ 11 - 0
README

@@ -67,6 +67,17 @@ Usage (of a compiled template)
 
         IOList is the rendered template.
 
+    my_compiled_template:render(Variables, Dictionary) -> 
+            {ok, IOList} | {error, Err}
+
+        Same as render/1, but Dictionary is a dict that will be used to 
+        translate strings appearing inside {% trans %} tags.
+
+    my_compiled_template:translatable_strings() -> [String]
+
+        List of strings appearing in {% trans %} tags that can be overridden 
+        with a dictionary passed to render/2.
+
     my_compiled_template:source() -> {FileName, CheckSum}
 
         Name and checksum of the original template file.

+ 1 - 1
src/erlydtl/erlydtl.app

@@ -1,7 +1,7 @@
 %% -*- mode: erlang -*-
 {application, erlydtl,
  [{description, "ErlyDTL implements most but not all of the Django Template Language"},
-  {vsn, "0.6.0"},
+  {vsn, "0.6.1"},
   {modules, [
              erlydtl,
              erlydtl_compiler,

+ 43 - 17
src/erlydtl/erlydtl_compiler.erl

@@ -52,10 +52,11 @@
     module = [],
     compiler_options = [verbose, report_errors],
     force_recompile = false,
-    locale}).
+    locale = none}).
 
 -record(ast_info, {
     dependencies = [],
+    translatable_strings = [],
     var_names = [],
     pre_render_asts = []}).
     
@@ -230,14 +231,22 @@ forms(File, Module, BodyAst, BodyInfo, CheckSum) ->
     Render0FunctionAst = erl_syntax:function(erl_syntax:atom(render),
         [erl_syntax:clause([], none, [erl_syntax:application(none, 
                         erl_syntax:atom(render), [erl_syntax:list([])])])]),
-    Function2 = erl_syntax:application(none, erl_syntax:atom(render2), 
-        [erl_syntax:variable("Variables")]),
+    Render1FunctionAst = erl_syntax:function(erl_syntax:atom(render),
+        [erl_syntax:clause([erl_syntax:variable("Variables")], none, 
+                [erl_syntax:application(none,
+                        erl_syntax:atom(render), 
+                        [erl_syntax:variable("Variables"), 
+                        erl_syntax:application(
+                            erl_syntax:atom(dict), erl_syntax:atom(new), [])])])]),
+    Function2 = erl_syntax:application(none, erl_syntax:atom(render_internal), 
+        [erl_syntax:variable("Variables"), erl_syntax:variable("Dictionary")]),
     ClauseOk = erl_syntax:clause([erl_syntax:variable("Val")], none,
         [erl_syntax:tuple([erl_syntax:atom(ok), erl_syntax:variable("Val")])]),     
     ClauseCatch = erl_syntax:clause([erl_syntax:variable("Err")], none,
         [erl_syntax:tuple([erl_syntax:atom(error), erl_syntax:variable("Err")])]),            
-    Render1FunctionAst = erl_syntax:function(erl_syntax:atom(render),
-        [erl_syntax:clause([erl_syntax:variable("Variables")], none, 
+    Render2FunctionAst = erl_syntax:function(erl_syntax:atom(render),
+        [erl_syntax:clause([erl_syntax:variable("Variables"), 
+                    erl_syntax:variable("Dictionary")], none, 
             [erl_syntax:try_expr([Function2], [ClauseOk], [ClauseCatch])])]),  
      
     SourceFunctionTuple = erl_syntax:tuple(
@@ -253,15 +262,19 @@ forms(File, Module, BodyAst, BodyInfo, CheckSum) ->
                         erl_syntax:tuple([erl_syntax:string(XFile), erl_syntax:string(XCheckSum)])
                 end, BodyInfo#ast_info.dependencies))])]),     
 
-   BodyAstTmp = erl_syntax:application(
+    TranslatableStringsAst = erl_syntax:function(
+        erl_syntax:atom(translatable_strings), [erl_syntax:clause([], none,
+                [erl_syntax:list(lists:map(fun(String) -> erl_syntax:string(String) end,
+                            BodyInfo#ast_info.translatable_strings))])]),
+
+    BodyAstTmp = erl_syntax:application(
                     erl_syntax:atom(erlydtl_runtime),
                     erl_syntax:atom(stringify_final),
-                    [BodyAst]
-                ),
+                    [BodyAst]),
 
     RenderInternalFunctionAst = erl_syntax:function(
-        erl_syntax:atom(render2), 
-            [erl_syntax:clause([erl_syntax:variable("Variables")], none, 
+        erl_syntax:atom(render_internal), 
+        [erl_syntax:clause([erl_syntax:variable("Variables"), erl_syntax:variable("Dictionary")], none, 
                 [BodyAstTmp])]),   
     
     ModuleAst  = erl_syntax:attribute(erl_syntax:atom(module), [erl_syntax:atom(Module)]),
@@ -269,11 +282,13 @@ forms(File, Module, BodyAst, BodyInfo, CheckSum) ->
     ExportAst = erl_syntax:attribute(erl_syntax:atom(export),
         [erl_syntax:list([erl_syntax:arity_qualifier(erl_syntax:atom(render), erl_syntax:integer(0)),
                     erl_syntax:arity_qualifier(erl_syntax:atom(render), erl_syntax:integer(1)),
+                    erl_syntax:arity_qualifier(erl_syntax:atom(render), erl_syntax:integer(2)),
                     erl_syntax:arity_qualifier(erl_syntax:atom(source), erl_syntax:integer(0)),
-                    erl_syntax:arity_qualifier(erl_syntax:atom(dependencies), erl_syntax:integer(0))])]),
+                    erl_syntax:arity_qualifier(erl_syntax:atom(dependencies), erl_syntax:integer(0)),
+                    erl_syntax:arity_qualifier(erl_syntax:atom(translatable_strings), erl_syntax:integer(0))])]),
     
-    [erl_syntax:revert(X) || X <- [ModuleAst, ExportAst, Render0FunctionAst,
-            Render1FunctionAst, SourceFunctionAst, DependenciesFunctionAst, RenderInternalFunctionAst
+    [erl_syntax:revert(X) || X <- [ModuleAst, ExportAst, Render0FunctionAst, Render1FunctionAst, Render2FunctionAst,
+            SourceFunctionAst, DependenciesFunctionAst, TranslatableStringsAst, RenderInternalFunctionAst
             | BodyInfo#ast_info.pre_render_asts]].    
 
         
@@ -446,6 +461,10 @@ merge_info(Info1, Info2) ->
             lists:merge(
                 lists:sort(Info1#ast_info.var_names), 
                 lists:sort(Info2#ast_info.var_names)),
+        translatable_strings =
+            lists:merge(
+                lists:sort(Info1#ast_info.translatable_strings),
+                lists:sort(Info2#ast_info.translatable_strings)),
         pre_render_asts = 
             lists:merge(
                 Info1#ast_info.pre_render_asts,
@@ -466,10 +485,17 @@ empty_ast(TreeWalker) ->
 
 
 translated_ast(String,Context, TreeWalker) ->
-        NewStr = string:sub_string(String, 2, string:len(String) -1),
-	Locale = Context#dtl_context.locale,
-        LocalizedString = erlydtl_i18n:translate(NewStr,Locale),
-        {{erl_syntax:string(LocalizedString), #ast_info{}}, TreeWalker}.
+        NewStr = unescape_string_literal(String),
+        DefaultString = case Context#dtl_context.locale of
+            none -> NewStr;
+            Locale -> erlydtl_i18n:translate(NewStr,Locale)
+        end,
+        StringLookupAst = erl_syntax:application(
+            erl_syntax:atom(erlydtl_runtime),
+            erl_syntax:atom(translate),
+            [erl_syntax:string(NewStr), erl_syntax:variable("Dictionary"), 
+                erl_syntax:string(DefaultString)]),
+        {{StringLookupAst, #ast_info{translatable_strings = [NewStr]}}, TreeWalker}.
 
 string_ast(String, TreeWalker) ->
     {{erl_syntax:string(String), #ast_info{}}, TreeWalker}. %% less verbose AST, better for development and debugging

+ 8 - 0
src/erlydtl/erlydtl_runtime.erl

@@ -50,6 +50,14 @@ fetch_value(Key, Data) ->
             Val
     end.
 
+translate(String, Dictionary, Default) ->
+    case dict:find(String, Dictionary) of
+        {ok, Val} ->
+            Val;
+        _ ->
+            Default
+    end.
+
 are_equal(Arg1, Arg2) when Arg1 =:= Arg2 ->
     true;
 are_equal(Arg1, Arg2) when is_binary(Arg1) ->

+ 18 - 7
src/tests/erlydtl_unittests.erl

@@ -90,8 +90,11 @@ tests() ->
 		  <<"Hello {% trans \"Hi\" %}">>, [], <<"Hello Hi">>
 		},
 		{"trans functional reverse locale",
-                  <<"Hello {% trans \"Hi\" %}">>, [], [{locale, "reverse"}], <<"Hello iH">>
-                }	
+                    <<"Hello {% trans \"Hi\" %}">>, [], dict:new(), [{locale, "reverse"}], <<"Hello iH">>
+                },
+                {"trans run-time lookup",
+                    <<"Hello {% trans \"Hi\" %}">>, [], dict:from_list([{"Hi", "Konichiwa"}]), [],
+                    <<"Hello Konichiwa">>}
 	]},
         {"if", [
                 {"If/else",
@@ -550,18 +553,26 @@ run_tests() ->
     Failures = lists:foldl(
         fun({Group, Assertions}, GroupAcc) ->
                 io:format(" Test group ~p...~n", [Group]),
-                lists:foldl(fun({Name, DTL, Vars, Output}, Acc) -> process_unit_test(erlydtl:compile(DTL, erlydtl_running_test, []),Vars, Output, Acc, Group, Name);
-                               ({Name, DTL, Vars, CompilerOpts, Output}, Acc) -> process_unit_test(erlydtl:compile(DTL, erlydtl_running_test, CompilerOpts),Vars, Output, Acc, Group, Name)
+                lists:foldl(fun
+                        ({Name, DTL, Vars, Output}, Acc) -> 
+                            process_unit_test(erlydtl:compile(DTL, erlydtl_running_test, []),
+                                Vars, dict:new(), Output, Acc, Group, Name);
+                        ({Name, DTL, Vars, Dictionary, Output}, Acc) -> 
+                            process_unit_test(erlydtl:compile(DTL, erlydtl_running_test, []), 
+                                Vars, Dictionary, Output, Acc, Group, Name);
+                        ({Name, DTL, Vars, Dictionary, CompilerOpts, Output}, Acc) -> 
+                            process_unit_test(erlydtl:compile(DTL, erlydtl_running_test, CompilerOpts), 
+                                Vars, Dictionary, Output, Acc, Group, Name)
                             end, GroupAcc, Assertions)
         end, [], tests()),
     
     io:format("Unit test failures: ~p~n", [lists:reverse(Failures)]).
 
-process_unit_test(CompiledTemplate, Vars, Output,Acc, Group, Name) ->
+process_unit_test(CompiledTemplate, Vars, Dictionary, Output,Acc, Group, Name) ->
 	case CompiledTemplate of
              {ok, _} ->
-                   {ok, IOList} = erlydtl_running_test:render(Vars),
-                   {ok, IOListBin} = erlydtl_running_test:render(vars_to_binary(Vars)),
+                   {ok, IOList} = erlydtl_running_test:render(Vars, Dictionary),
+                   {ok, IOListBin} = erlydtl_running_test:render(vars_to_binary(Vars), Dictionary),
                    case {iolist_to_binary(IOList), iolist_to_binary(IOListBin)} of
                         {Output, Output} ->
 	                          Acc;