Permalink
Browse files

Use a translation fun instead of translation dict.

Also fix a bug in po_scanner, and support the "noop" option in
{% trans %} tags.
  • Loading branch information...
evanmiller committed May 27, 2010
1 parent 550bbe4 commit 1a719d9faee5a5aaf0feb04151e7060fe66e4d4a
View
9 README
@@ -67,11 +67,14 @@ Usage (of a compiled template)
IOList is the rendered template.
- my_compiled_template:render(Variables, Dictionary) ->
+ my_compiled_template:render(Variables, TranslationFun) ->
{ok, IOList} | {error, Err}
- Same as render/1, but Dictionary is a dict that will be used to
- translate strings appearing inside {% trans %} tags.
+ Same as render/1, but TranslationFun is a fun/1 that will be used to
+ translate strings appearing inside {% trans %} tags. The simplest
+ TranslatioFun would be
+
+ fun(Val) -> Val end
my_compiled_template:translatable_strings() -> [String]
@@ -234,19 +234,17 @@ forms(File, Module, BodyAst, BodyInfo, CheckSum) ->
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), [])])])]),
+ erl_syntax:atom(render),
+ [erl_syntax:variable("Variables"), erl_syntax:atom(none)])])]),
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,
[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")])]),
Render2FunctionAst = erl_syntax:function(erl_syntax:atom(render),
[erl_syntax:clause([erl_syntax:variable("Variables"),
- erl_syntax:variable("Dictionary")], none,
+ erl_syntax:variable("TranslationFun")], none,
[erl_syntax:try_expr([Function2], [ClauseOk], [ClauseCatch])])]),
SourceFunctionTuple = erl_syntax:tuple(
@@ -274,7 +272,7 @@ forms(File, Module, BodyAst, BodyInfo, CheckSum) ->
RenderInternalFunctionAst = erl_syntax:function(
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])]),
ModuleAst = erl_syntax:attribute(erl_syntax:atom(module), [erl_syntax:atom(Module)]),
@@ -338,8 +336,8 @@ body_ast(DjangoParseTree, Context, TreeWalker) ->
TreeWalkerAcc);
({'string', _Pos, String}, TreeWalkerAcc) ->
string_ast(String, TreeWalkerAcc);
- ({'trans', {string_literal, _Pos, FormatString}}, TreeWalkerAcc) ->
- translated_ast(FormatString, Context, TreeWalkerAcc);
+ ({'trans', Value}, TreeWalkerAcc) ->
+ translated_ast(Value, Context, TreeWalkerAcc);
({'include', {string_literal, _, File}}, TreeWalkerAcc) ->
include_ast(unescape_string_literal(File), Context, TreeWalkerAcc);
({'if', Expression, Contents}, TreeWalkerAcc) ->
@@ -484,18 +482,24 @@ empty_ast(TreeWalker) ->
{{erl_syntax:list([]), #ast_info{}}, TreeWalker}.
-translated_ast(String,Context, 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}.
+translated_ast({string_literal, _, String}, Context, TreeWalker) ->
+ NewStr = unescape_string_literal(String),
+ DefaultString = case Context#dtl_context.locale of
+ none -> NewStr;
+ Locale -> erlydtl_i18n:translate(NewStr,Locale)
+ end,
+ translated_ast2(erl_syntax:string(NewStr), erl_syntax:string(DefaultString),
+ #ast_info{translatable_strings = [NewStr]}, TreeWalker);
+translated_ast(ValueToken, Context, TreeWalker) ->
+ {{Ast, Info}, TreeWalker1} = value_ast(ValueToken, true, Context, TreeWalker),
+ translated_ast2(Ast, Ast, Info, TreeWalker1).
+
+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) ->
{{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, 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),
- [VariableAst | case Arg of
- [{string_literal, _, ArgName}] ->
- [erl_syntax:string(unescape_string_literal(ArgName))];
- [{number_literal, _, ArgName}] ->
- [erl_syntax:integer(list_to_integer(ArgName))];
- _ ->
- []
- end]).
+ [VariableAst | AdditionalArgs]).
search_for_escape_filter(_, _, #dtl_context{auto_escape = on}) ->
on;
@@ -130,6 +130,7 @@ Terminals
in_keyword
include_keyword
load_keyword
+ noop_keyword
not_keyword
now_keyword
number_literal
@@ -183,9 +184,12 @@ Value -> Variable : '$1'.
Value -> Literal : '$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 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'}.
IncludeTag -> open_tag include_keyword string_literal close_tag : {include, '$3'}.
@@ -50,12 +50,14 @@ fetch_value(Key, Data) ->
Val
end.
-translate(String, Dictionary, Default) ->
- case dict:find(String, Dictionary) of
- {ok, Val} ->
- Val;
- _ ->
- Default
+translate(_, none, Default) ->
+ Default;
+translate(String, TranslationFun, Default) when is_binary(String) ->
+ translate(binary_to_list(String), TranslationFun, Default);
+translate(String, TranslationFun, Default) when is_function(TranslationFun) ->
+ case TranslationFun(String) of
+ undefined -> Default;
+ Str -> Str
end.
are_equal(Arg1, Arg2) when Arg1 =:= Arg2 ->
@@ -62,7 +62,7 @@ scan([], Scanned, _, in_text) ->
"not", "or", "and", "comment", "endcomment", "cycle", "firstof",
"ifchanged", "ifequal", "endifequal", "ifnotequal", "endifnotequal",
"now", "regroup", "spaceless", "endspaceless", "ssi", "templatetag",
- "load", "call", "with", "trans"],
+ "load", "call", "with", "trans", "noop"],
Type = case lists:member(RevString, Keywords) of
true ->
list_to_atom(RevString ++ "_keyword");
@@ -25,7 +25,7 @@ scan(Path) ->
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("\n" ++ T, Scanned, {Row, _Column}, [{in_comment, Comment}|Status]) ->
scan(T, lists:append(Scanned, [{comment, Comment}]), {Row +1 , 1}, Status);
@@ -90,11 +90,20 @@ tests() ->
<<"Hello {% trans \"Hi\" %}">>, [], <<"Hello Hi">>
},
{"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",
- <<"Hello {% trans \"Hi\" %}">>, [], dict:from_list([{"Hi", "Konichiwa"}]), [],
- <<"Hello Konichiwa">>}
+ {"trans literal at run-time",
+ <<"Hello {% trans \"Hi\" %}">>, [], fun("Hi") -> "Konichiwa" end, [],
+ <<"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/else",
@@ -556,7 +565,7 @@ run_tests() ->
lists:foldl(fun
({Name, DTL, Vars, Output}, Acc) ->
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) ->
process_unit_test(erlydtl:compile(DTL, erlydtl_running_test, []),
Vars, Dictionary, Output, Acc, Group, Name);

0 comments on commit 1a719d9

Please sign in to comment.