Browse Source

Use a translation fun instead of translation dict.

Also fix a bug in po_scanner, and support the "noop" option in
{% trans %} tags.
Evan Miller 15 years ago
parent
commit
1a719d9fae

+ 6 - 3
README

@@ -67,11 +67,14 @@ Usage (of a compiled template)
 
 
         IOList is the rendered template.
         IOList is the rendered template.
 
 
-    my_compiled_template:render(Variables, Dictionary) -> 
+    my_compiled_template:render(Variables, TranslationFun) -> 
             {ok, IOList} | {error, Err}
             {ok, IOList} | {error, Err}
 
 
-        Same as render/1, but Dictionary is a dict that will be used to 
+        Same as render/1, but TranslationFun is a fun/1 that will be used to 
-        translate strings appearing inside {% trans %} tags.
+        translate strings appearing inside {% trans %} tags. The simplest
+        TranslatioFun would be
+
+            fun(Val) -> Val end
 
 
     my_compiled_template:translatable_strings() -> [String]
     my_compiled_template:translatable_strings() -> [String]
 
 

+ 34 - 30
src/erlydtl/erlydtl_compiler.erl

@@ -234,19 +234,17 @@ forms(File, Module, BodyAst, BodyInfo, CheckSum) ->
     Render1FunctionAst = erl_syntax:function(erl_syntax:atom(render),
     Render1FunctionAst = erl_syntax:function(erl_syntax:atom(render),
         [erl_syntax:clause([erl_syntax:variable("Variables")], none, 
         [erl_syntax:clause([erl_syntax:variable("Variables")], none, 
                 [erl_syntax:application(none,
                 [erl_syntax:application(none,
-                        erl_syntax:atom(render), 
+                        erl_syntax:atom(render),
-                        [erl_syntax:variable("Variables"), 
+                        [erl_syntax:variable("Variables"), erl_syntax:atom(none)])])]),
-                        erl_syntax:application(
-                            erl_syntax:atom(dict), erl_syntax:atom(new), [])])])]),
     Function2 = erl_syntax:application(none, erl_syntax:atom(render_internal), 
     Function2 = erl_syntax:application(none, erl_syntax:atom(render_internal), 
-        [erl_syntax:variable("Variables"), erl_syntax:variable("Dictionary")]),
+        [erl_syntax:variable("Variables"), erl_syntax:variable("TranslationFun")]),
     ClauseOk = erl_syntax:clause([erl_syntax:variable("Val")], none,
     ClauseOk = erl_syntax:clause([erl_syntax:variable("Val")], none,
         [erl_syntax:tuple([erl_syntax:atom(ok), erl_syntax:variable("Val")])]),     
         [erl_syntax:tuple([erl_syntax:atom(ok), erl_syntax:variable("Val")])]),     
     ClauseCatch = erl_syntax:clause([erl_syntax:variable("Err")], none,
     ClauseCatch = erl_syntax:clause([erl_syntax:variable("Err")], none,
         [erl_syntax:tuple([erl_syntax:atom(error), erl_syntax:variable("Err")])]),            
         [erl_syntax:tuple([erl_syntax:atom(error), erl_syntax:variable("Err")])]),            
     Render2FunctionAst = erl_syntax:function(erl_syntax:atom(render),
     Render2FunctionAst = erl_syntax:function(erl_syntax:atom(render),
         [erl_syntax:clause([erl_syntax:variable("Variables"), 
         [erl_syntax:clause([erl_syntax:variable("Variables"), 
-                    erl_syntax:variable("Dictionary")], none, 
+                    erl_syntax:variable("TranslationFun")], none, 
             [erl_syntax:try_expr([Function2], [ClauseOk], [ClauseCatch])])]),  
             [erl_syntax:try_expr([Function2], [ClauseOk], [ClauseCatch])])]),  
      
      
     SourceFunctionTuple = erl_syntax:tuple(
     SourceFunctionTuple = erl_syntax:tuple(
@@ -274,7 +272,7 @@ forms(File, Module, BodyAst, BodyInfo, CheckSum) ->
 
 
     RenderInternalFunctionAst = erl_syntax:function(
     RenderInternalFunctionAst = erl_syntax:function(
         erl_syntax:atom(render_internal), 
         erl_syntax:atom(render_internal), 
-        [erl_syntax:clause([erl_syntax:variable("Variables"), erl_syntax:variable("Dictionary")], none, 
+        [erl_syntax:clause([erl_syntax:variable("Variables"), erl_syntax:variable("TranslationFun")], none, 
                 [BodyAstTmp])]),   
                 [BodyAstTmp])]),   
     
     
     ModuleAst  = erl_syntax:attribute(erl_syntax:atom(module), [erl_syntax:atom(Module)]),
     ModuleAst  = erl_syntax:attribute(erl_syntax:atom(module), [erl_syntax:atom(Module)]),
@@ -338,8 +336,8 @@ body_ast(DjangoParseTree, Context, TreeWalker) ->
                     TreeWalkerAcc);
                     TreeWalkerAcc);
             ({'string', _Pos, String}, TreeWalkerAcc) -> 
             ({'string', _Pos, String}, TreeWalkerAcc) -> 
                 string_ast(String, TreeWalkerAcc);
                 string_ast(String, TreeWalkerAcc);
-	    ({'trans', {string_literal, _Pos, FormatString}}, TreeWalkerAcc) ->
+	    ({'trans', Value}, TreeWalkerAcc) ->
-                translated_ast(FormatString, Context, TreeWalkerAcc);
+                translated_ast(Value, Context, TreeWalkerAcc);
             ({'include', {string_literal, _, File}}, TreeWalkerAcc) ->
             ({'include', {string_literal, _, File}}, TreeWalkerAcc) ->
                 include_ast(unescape_string_literal(File), Context, TreeWalkerAcc);
                 include_ast(unescape_string_literal(File), Context, TreeWalkerAcc);
             ({'if', Expression, Contents}, TreeWalkerAcc) ->
             ({'if', Expression, Contents}, TreeWalkerAcc) ->
@@ -484,18 +482,24 @@ empty_ast(TreeWalker) ->
     {{erl_syntax:list([]), #ast_info{}}, TreeWalker}.
     {{erl_syntax:list([]), #ast_info{}}, TreeWalker}.
 
 
 
 
-translated_ast(String,Context, TreeWalker) ->
+translated_ast({string_literal, _, String}, Context, TreeWalker) ->
-        NewStr = unescape_string_literal(String),
+    NewStr = unescape_string_literal(String),
-        DefaultString = case Context#dtl_context.locale of
+    DefaultString = case Context#dtl_context.locale of
-            none -> NewStr;
+        none -> NewStr;
-            Locale -> erlydtl_i18n:translate(NewStr,Locale)
+        Locale -> erlydtl_i18n:translate(NewStr,Locale)
-        end,
+    end,
-        StringLookupAst = erl_syntax:application(
+    translated_ast2(erl_syntax:string(NewStr), erl_syntax:string(DefaultString),
-            erl_syntax:atom(erlydtl_runtime),
+        #ast_info{translatable_strings = [NewStr]}, TreeWalker);
-            erl_syntax:atom(translate),
+translated_ast(ValueToken, Context, TreeWalker) ->
-            [erl_syntax:string(NewStr), erl_syntax:variable("Dictionary"), 
+    {{Ast, Info}, TreeWalker1} = value_ast(ValueToken, true, Context, TreeWalker),
-                erl_syntax:string(DefaultString)]),
+    translated_ast2(Ast, Ast, Info, TreeWalker1).
-        {{StringLookupAst, #ast_info{translatable_strings = [NewStr]}}, TreeWalker}.
+
+translated_ast2(NewStrAst, DefaultStringAst, AstInfo, TreeWalker) ->
+    StringLookupAst = erl_syntax:application(
+        erl_syntax:atom(erlydtl_runtime),
+        erl_syntax:atom(translate),
+        [NewStrAst, erl_syntax:variable("TranslationFun"), DefaultStringAst]),
+    {{StringLookupAst, AstInfo}, TreeWalker}.
 
 
 string_ast(String, TreeWalker) ->
 string_ast(String, TreeWalker) ->
     {{erl_syntax:string(String), #ast_info{}}, TreeWalker}. %% less verbose AST, better for development and debugging
     {{erl_syntax:string(String), #ast_info{}}, TreeWalker}. %% less verbose AST, better for development and debugging
@@ -538,16 +542,16 @@ filter_ast_noescape(Variable, Filter, Context, TreeWalker) ->
     VarValue = filter_ast1(Filter, VariableAst),
     VarValue = filter_ast1(Filter, VariableAst),
     {{VarValue, Info}, TreeWalker2}.
     {{VarValue, Info}, TreeWalker2}.
 
 
-filter_ast1([{identifier, _, Name} | Arg], VariableAst) ->
+filter_ast1([{identifier, _, Name}, {string_literal, _, ArgName}], VariableAst) ->
+    filter_ast2(Name, VariableAst, [erl_syntax:string(unescape_string_literal(ArgName))]);
+filter_ast1([{identifier, _, Name}, {number_literal, _, ArgName}], VariableAst) ->
+    filter_ast2(Name, VariableAst, [erl_syntax:integer(list_to_integer(ArgName))]);
+filter_ast1([{identifier, _, Name}|_], VariableAst) ->
+    filter_ast2(Name, VariableAst, []).
+
+filter_ast2(Name, VariableAst, AdditionalArgs) ->
     erl_syntax:application(erl_syntax:atom(erlydtl_filters), erl_syntax:atom(Name), 
     erl_syntax:application(erl_syntax:atom(erlydtl_filters), erl_syntax:atom(Name), 
-        [VariableAst | case Arg of 
+        [VariableAst | AdditionalArgs]).
-                [{string_literal, _, ArgName}] ->
-                    [erl_syntax:string(unescape_string_literal(ArgName))];
-                [{number_literal, _, ArgName}] ->
-                    [erl_syntax:integer(list_to_integer(ArgName))];
-                _ ->
-                    []
-            end]).
  
  
 search_for_escape_filter(_, _, #dtl_context{auto_escape = on}) ->
 search_for_escape_filter(_, _, #dtl_context{auto_escape = on}) ->
     on;
     on;

+ 5 - 1
src/erlydtl/erlydtl_parser.yrl

@@ -130,6 +130,7 @@ Terminals
     in_keyword
     in_keyword
     include_keyword
     include_keyword
     load_keyword
     load_keyword
+    noop_keyword
     not_keyword
     not_keyword
     now_keyword
     now_keyword
     number_literal
     number_literal
@@ -183,9 +184,12 @@ Value -> Variable : '$1'.
 Value -> Literal : '$1'.
 Value -> Literal : '$1'.
 
 
 Variable -> identifier : {variable, '$1'}.
 Variable -> identifier : {variable, '$1'}.
-Variable -> Value '.' identifier : {attribute, {'$3', '$1'}}.
+Variable -> Variable '.' identifier : {attribute, {'$3', '$1'}}.
 
 
 TransTag -> open_tag trans_keyword string_literal close_tag : {trans, '$3'}.
 TransTag -> open_tag trans_keyword string_literal close_tag : {trans, '$3'}.
+TransTag -> open_tag trans_keyword Variable close_tag : {trans, '$3'}.
+TransTag -> open_tag trans_keyword string_literal noop_keyword close_tag : '$3'.
+TransTag -> open_tag trans_keyword Variable noop_keyword close_tag : '$3'.
 
 
 ExtendsTag -> open_tag extends_keyword string_literal close_tag : {extends, '$3'}.
 ExtendsTag -> open_tag extends_keyword string_literal close_tag : {extends, '$3'}.
 IncludeTag -> open_tag include_keyword string_literal close_tag : {include, '$3'}.
 IncludeTag -> open_tag include_keyword string_literal close_tag : {include, '$3'}.

+ 8 - 6
src/erlydtl/erlydtl_runtime.erl

@@ -50,12 +50,14 @@ fetch_value(Key, Data) ->
             Val
             Val
     end.
     end.
 
 
-translate(String, Dictionary, Default) ->
+translate(_, none, Default) ->
-    case dict:find(String, Dictionary) of
+    Default;
-        {ok, Val} ->
+translate(String, TranslationFun, Default) when is_binary(String) ->
-            Val;
+    translate(binary_to_list(String), TranslationFun, Default);
-        _ ->
+translate(String, TranslationFun, Default) when is_function(TranslationFun) ->
-            Default
+    case TranslationFun(String) of
+        undefined -> Default;
+        Str -> Str
     end.
     end.
 
 
 are_equal(Arg1, Arg2) when Arg1 =:= Arg2 ->
 are_equal(Arg1, Arg2) when Arg1 =:= Arg2 ->

+ 1 - 1
src/erlydtl/erlydtl_scanner.erl

@@ -62,7 +62,7 @@ scan([], Scanned, _, in_text) ->
                             "not", "or", "and", "comment", "endcomment", "cycle", "firstof",
                             "not", "or", "and", "comment", "endcomment", "cycle", "firstof",
                             "ifchanged", "ifequal", "endifequal", "ifnotequal", "endifnotequal",
                             "ifchanged", "ifequal", "endifequal", "ifnotequal", "endifnotequal",
                             "now", "regroup", "spaceless", "endspaceless", "ssi", "templatetag",
                             "now", "regroup", "spaceless", "endspaceless", "ssi", "templatetag",
-                            "load", "call", "with", "trans"], 
+                            "load", "call", "with", "trans", "noop"], 
                         Type = case lists:member(RevString, Keywords) of
                         Type = case lists:member(RevString, Keywords) of
                             true ->
                             true ->
                                 list_to_atom(RevString ++ "_keyword");
                                 list_to_atom(RevString ++ "_keyword");

+ 1 - 1
src/erlydtl/i18n/po_scanner.erl

@@ -25,7 +25,7 @@ scan(Path) ->
 	end.
 	end.
 
 
 
 
-scan("#" ++ T, Scanned, {Row, Column}, Status) -> 
+scan("#" ++ T, Scanned, {Row, Column}, Status = [in_text]) -> 
 	scan(T, Scanned, {Row, Column + 1}, lists:append([{in_comment, []}],Status));
 	scan(T, Scanned, {Row, Column + 1}, lists:append([{in_comment, []}],Status));
 scan("\n" ++ T, Scanned, {Row, _Column}, [{in_comment, Comment}|Status]) -> 
 scan("\n" ++ T, Scanned, {Row, _Column}, [{in_comment, Comment}|Status]) -> 
 	scan(T, lists:append(Scanned, [{comment, Comment}]), {Row +1 , 1}, Status);
 	scan(T, lists:append(Scanned, [{comment, Comment}]), {Row +1 , 1}, Status);

+ 14 - 5
src/tests/erlydtl_unittests.erl

@@ -90,11 +90,20 @@ tests() ->
 		  <<"Hello {% trans \"Hi\" %}">>, [], <<"Hello Hi">>
 		  <<"Hello {% trans \"Hi\" %}">>, [], <<"Hello Hi">>
 		},
 		},
 		{"trans functional reverse locale",
 		{"trans functional reverse locale",
-                    <<"Hello {% trans \"Hi\" %}">>, [], dict:new(), [{locale, "reverse"}], <<"Hello iH">>
+                    <<"Hello {% trans \"Hi\" %}">>, [], none, [{locale, "reverse"}], <<"Hello iH">>
                 },
                 },
-                {"trans run-time lookup",
+                {"trans literal at run-time",
-                    <<"Hello {% trans \"Hi\" %}">>, [], dict:from_list([{"Hi", "Konichiwa"}]), [],
+                    <<"Hello {% trans \"Hi\" %}">>, [], fun("Hi") -> "Konichiwa" end, [],
-                    <<"Hello Konichiwa">>}
+                    <<"Hello Konichiwa">>},
+                {"trans variable at run-time",
+                    <<"Hello {% trans var1 %}">>, [{var1, "Hi"}], fun("Hi") -> "Konichiwa" end, [],
+                    <<"Hello Konichiwa">>},
+                {"trans literal at run-time: No-op",
+                    <<"Hello {% trans \"Hi\" noop %}">>, [], fun("Hi") -> "Konichiwa" end, [],
+                    <<"Hello Hi">>},
+                {"trans variable at run-time: No-op",
+                    <<"Hello {% trans var1 noop %}">>, [{var1, "Hi"}], fun("Hi") -> "Konichiwa" end, [],
+                    <<"Hello Hi">>}
 	]},
 	]},
         {"if", [
         {"if", [
                 {"If/else",
                 {"If/else",
@@ -556,7 +565,7 @@ run_tests() ->
                 lists:foldl(fun
                 lists:foldl(fun
                         ({Name, DTL, Vars, Output}, Acc) -> 
                         ({Name, DTL, Vars, Output}, Acc) -> 
                             process_unit_test(erlydtl:compile(DTL, erlydtl_running_test, []),
                             process_unit_test(erlydtl:compile(DTL, erlydtl_running_test, []),
-                                Vars, dict:new(), Output, Acc, Group, Name);
+                                Vars, none, Output, Acc, Group, Name);
                         ({Name, DTL, Vars, Dictionary, Output}, Acc) -> 
                         ({Name, DTL, Vars, Dictionary, Output}, Acc) -> 
                             process_unit_test(erlydtl:compile(DTL, erlydtl_running_test, []), 
                             process_unit_test(erlydtl:compile(DTL, erlydtl_running_test, []), 
                                 Vars, Dictionary, Output, Acc, Group, Name);
                                 Vars, Dictionary, Output, Acc, Group, Name);