Browse Source

{% blocktrans %} no longer uses identifiers

Previously {% blocktrans %} blocks required an identifier which would be
passed as an atom to the blocktrans_fun function passed in at
compile-time. This behavior was inconsistent with Django. Now identifiers
are no longer used, and the blocktrans_fun function simply receives the
block to be translated as a string.

For convenience, compiled modules now have a translated_blocks/0
function which returns a list of blocks that were translated at
compile-time. A new helper module called blocktrans_extractor is
provided for extracting blocktrans blocks prior to compilation.

The implementation may result in some oddities; the translatable blocks
are actually derived from the abstract syntax tree, so there may
be whitespace or small syntactic differences between the blocks in
the source code and the blocks used at translation-time. However,
the blocktrans_extractor module will return blocks that are exactly
compatible with the translatable blocks. One side-benefit of this
appproach is that non-semantic whitespace will always be ignored;
if two {% blocktrans %} blocks have slight syntactic differences
but are semantically the same, you only need to provide a single
translation to cover the two of them.
Evan Miller 13 years ago
parent
commit
dc377d7581

+ 10 - 3
README.markdown

@@ -82,7 +82,7 @@ See README_I18N.
 blocks. This will be called once for each pair of `blocktrans` block and locale
 specified in `blocktrans_locales`. The fun should take the form:
 
-    Fun(BlockName, Locale) -> <<"ErlyDTL code">> | default
+    Fun(Block::string(), Locale::string()) -> <<"ErlyDTL code">> | default
 
 * `blocktrans_locales` - A list of locales to be passed to `blocktrans_fun`.
 Defaults to [].
@@ -137,8 +137,15 @@ Val end`
 
     my_compiled_template:translatable_strings() -> [String]
 
-List of strings appearing in `{% trans %}` tags that can be overridden 
-with a dictionary passed to `render/2`.
+List of strings appearing in `{% trans %}` tags that can be overridden with
+a dictionary passed to `render/2`.
+
+    my_compiled_template:translated_blocks() -> [String]
+
+List of strings appearing in `{% blocktrans %}...{% endblocktrans %}` blocks;
+the translations (which can contain ErlyDTL code) are hard-coded into the
+module and appear at render-time. To get a list of translatable blocks before
+compile-time, use the provided `blocktrans_extractor` module.
 
     my_compiled_template:source() -> {FileName, CheckSum}
 

+ 30 - 13
src/erlydtl_compiler.erl

@@ -62,7 +62,7 @@
 -record(ast_info, {
     dependencies = [],
     translatable_strings = [],
-    translatable_blocks= [],
+    translated_blocks= [],
     custom_tags = [],
     var_names = [],
     pre_render_asts = []}).
@@ -372,6 +372,14 @@ translatable_strings_function(TranslatableStrings) ->
                             end,
                             TranslatableStrings))])]).
 
+translated_blocks_function(TranslatedBlocks) ->
+        erl_syntax:function(
+        erl_syntax:atom(translated_blocks), [erl_syntax:clause([], none,
+                [erl_syntax:list(lists:map(fun(String) -> 
+                                    erl_syntax:string(String) 
+                            end,
+                            TranslatedBlocks))])]).
+
 custom_forms(Dir, Module, Functions, AstInfo) ->
     ModuleAst = erl_syntax:attribute(erl_syntax:atom(module), [erl_syntax:atom(Module)]),
     ExportAst = erl_syntax:attribute(erl_syntax:atom(export),
@@ -438,6 +446,8 @@ forms(File, Module, {BodyAst, BodyInfo}, {CustomTagsFunctionAst, CustomTagsInfo}
 
     TranslatableStringsAst = translatable_strings_function(MergedInfo#ast_info.translatable_strings),
 
+    TranslatedBlocksAst = translated_blocks_function(MergedInfo#ast_info.translated_blocks),
+
     BodyAstTmp = erl_syntax:application(
                     erl_syntax:atom(erlydtl_runtime),
                     erl_syntax:atom(stringify_final),
@@ -457,15 +467,17 @@ forms(File, Module, {BodyAst, BodyInfo}, {CustomTagsFunctionAst, CustomTagsInfo}
                     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(translatable_strings), erl_syntax:integer(0))])]),
+                    erl_syntax:arity_qualifier(erl_syntax:atom(translatable_strings), erl_syntax:integer(0)),
+                    erl_syntax:arity_qualifier(erl_syntax:atom(translated_blocks), erl_syntax:integer(0))
+                ])]),
     
     [erl_syntax:revert(X) || X <- [ModuleAst, ExportAst, Render0FunctionAst, Render1FunctionAst, Render2FunctionAst,
-            SourceFunctionAst, DependenciesFunctionAst, TranslatableStringsAst, RenderInternalFunctionAst, CustomTagsFunctionAst
-            | BodyInfo#ast_info.pre_render_asts]].    
+            SourceFunctionAst, DependenciesFunctionAst, TranslatableStringsAst, TranslatedBlocksAst, RenderInternalFunctionAst, 
+            CustomTagsFunctionAst | BodyInfo#ast_info.pre_render_asts]].    
 
         
 % child templates should only consist of blocks at the top level
-body_ast([{extends, {string_literal, _Pos, String}} | ThisParseTree], Context, TreeWalker) ->
+body_ast([{'extends', {string_literal, _Pos, String}} | ThisParseTree], Context, TreeWalker) ->
     File = full_path(unescape_string_literal(String), Context#dtl_context.doc_root),
     case lists:member(File, Context#dtl_context.parse_trail) of
         true ->
@@ -502,11 +514,11 @@ body_ast(DjangoParseTree, Context, TreeWalker) ->
                     _ -> Contents
                 end,
                 body_ast(Block, Context, TreeWalkerAcc);
-            ({'blocktrans', {identifier, _, Name}, Args, Contents}, TreeWalkerAcc) ->
-                blocktrans_ast(Name, Args, Contents, Context, TreeWalkerAcc);
-            ({'call', {'identifier', _, Name}}, TreeWalkerAcc) ->
+            ({'blocktrans', Args, Contents}, TreeWalkerAcc) ->
+                blocktrans_ast(Args, Contents, Context, TreeWalkerAcc);
+            ({'call', {identifier, _, Name}}, TreeWalkerAcc) ->
             	call_ast(Name, TreeWalkerAcc);
-            ({'call', {'identifier', _, Name}, With}, TreeWalkerAcc) ->
+            ({'call', {identifier, _, Name}, With}, TreeWalkerAcc) ->
             	call_with_ast(Name, With, Context, TreeWalkerAcc);
             ({'comment', _Contents}, TreeWalkerAcc) ->
                 empty_ast(TreeWalkerAcc);
@@ -562,7 +574,7 @@ body_ast(DjangoParseTree, Context, TreeWalker) ->
                 include_ast(unescape_string_literal(FileName), [], Context#dtl_context.local_scopes, Context, TreeWalkerAcc);
             ({'string', _Pos, String}, TreeWalkerAcc) -> 
                 string_ast(String, Context, TreeWalkerAcc);
-            ({'tag', {'identifier', _, Name}, Args}, TreeWalkerAcc) ->
+            ({'tag', {identifier, _, Name}, Args}, TreeWalkerAcc) ->
                 tag_ast(Name, Args, Context, TreeWalkerAcc);            
             ({'templatetag', {_, _, TagName}}, TreeWalkerAcc) ->
                 templatetag_ast(TagName, Context, TreeWalkerAcc);
@@ -651,6 +663,10 @@ merge_info(Info1, Info2) ->
             lists:merge(
                 lists:sort(Info1#ast_info.translatable_strings),
                 lists:sort(Info2#ast_info.translatable_strings)),
+        translated_blocks =
+            lists:merge(
+                lists:sort(Info1#ast_info.translated_blocks),
+                lists:sort(Info2#ast_info.translated_blocks)),
         custom_tags = 
             lists:merge(
                 lists:sort(Info1#ast_info.custom_tags),
@@ -673,13 +689,14 @@ with_dependency(FilePath, {{Ast, Info}, TreeWalker}) ->
 empty_ast(TreeWalker) ->
     {{erl_syntax:list([]), #ast_info{}}, TreeWalker}.
 
-blocktrans_ast(Name, ArgList, Contents, Context, TreeWalker) ->
+blocktrans_ast(ArgList, Contents, Context, TreeWalker) ->
     {NewScope, {ArgInfo, TreeWalker1}} = lists:mapfoldl(fun
             ({{identifier, _, LocalVarName}, Value}, {AstInfo1, TreeWalker1}) ->
                 {{Ast, Info}, TreeWalker2} = value_ast(Value, false, Context, TreeWalker1),
                 {{LocalVarName, Ast}, {merge_info(AstInfo1, Info), TreeWalker2}}
         end, {#ast_info{}, TreeWalker}, ArgList),
     NewContext = Context#dtl_context{ local_scopes = [NewScope|Context#dtl_context.local_scopes] },
+    SourceText = lists:flatten(erlydtl_unparser:unparse(Contents)),
     {{DefaultAst, AstInfo}, TreeWalker2} = body_ast(Contents, NewContext, TreeWalker1),
     MergedInfo = merge_info(AstInfo, ArgInfo),
     case Context#dtl_context.blocktrans_fun of
@@ -687,7 +704,7 @@ blocktrans_ast(Name, ArgList, Contents, Context, TreeWalker) ->
             {{DefaultAst, MergedInfo}, TreeWalker2};
         BlockTransFun when is_function(BlockTransFun) ->
             {FinalAstInfo, FinalTreeWalker, Clauses} = lists:foldr(fun(Locale, {AstInfoAcc, ThisTreeWalker, ClauseAcc}) ->
-                        case BlockTransFun(Name, Locale) of
+                        case BlockTransFun(SourceText, Locale) of
                             default ->
                                 {AstInfoAcc, ThisTreeWalker, ClauseAcc};
                             Body ->
@@ -699,7 +716,7 @@ blocktrans_ast(Name, ArgList, Contents, Context, TreeWalker) ->
                 end, {MergedInfo, TreeWalker2, []}, Context#dtl_context.blocktrans_locales),
             Ast = erl_syntax:case_expr(erl_syntax:variable("CurrentLocale"),
                 Clauses ++ [erl_syntax:clause([erl_syntax:underscore()], none, [DefaultAst])]),
-            {{Ast, FinalAstInfo}, FinalTreeWalker}
+            {{Ast, FinalAstInfo#ast_info{ translated_blocks = [SourceText] }}, FinalTreeWalker}
     end.
 
 translated_ast({string_literal, _, String}, Context, TreeWalker) ->

+ 2 - 2
src/erlydtl_parser.yrl

@@ -318,8 +318,8 @@ SpacelessBlock -> open_tag spaceless_keyword close_tag Elements open_tag endspac
 SSITag -> open_tag ssi_keyword Value close_tag : {ssi, '$3'}.
 SSITag -> open_tag ssi_keyword string_literal parsed_keyword close_tag : {ssi_parsed, '$3'}.
 
-BlockTransBlock -> open_tag blocktrans_keyword identifier close_tag Elements open_tag endblocktrans_keyword close_tag : {blocktrans, '$3', [], '$5'}.
-BlockTransBlock -> open_tag blocktrans_keyword identifier with_keyword Args close_tag Elements open_tag endblocktrans_keyword close_tag : {blocktrans, '$3', '$5', '$7'}.
+BlockTransBlock -> open_tag blocktrans_keyword close_tag Elements open_tag endblocktrans_keyword close_tag : {blocktrans, [], '$4'}.
+BlockTransBlock -> open_tag blocktrans_keyword with_keyword Args close_tag Elements open_tag endblocktrans_keyword close_tag : {blocktrans, '$4', '$6'}.
 
 TemplatetagTag -> open_tag templatetag_keyword Templatetag close_tag : {templatetag, '$3'}.
 

+ 181 - 0
src/erlydtl_unparser.erl

@@ -0,0 +1,181 @@
+-module(erlydtl_unparser).
+-export([unparse/1]).
+
+unparse(DjangoParseTree) ->
+    unparse(DjangoParseTree, []).
+
+unparse([], Acc) ->
+    lists:reverse(Acc);
+unparse([{'extends', Value}|Rest], Acc) ->
+    unparse(Rest, [["{% extends ", unparse_value(Value), " %}"]|Acc]);
+unparse([{'autoescape', OnOrOff, Contents}|Rest], Acc) ->
+    unparse(Rest, [["{% autoescape ", unparse_identifier(OnOrOff), " %}", unparse(Contents), "{% endautoescape %}"]|Acc]);
+unparse([{'block', Identifier, Contents}|Rest], Acc) ->
+    unparse(Rest, [["{% block ", unparse_identifier(Identifier), " %}", unparse(Contents), "{% endblock %}"]|Acc]);
+unparse([{'blocktrans', [], Contents}|Rest], Acc) ->
+    unparse(Rest, [["{% blocktrans %}", unparse(Contents), "{% endblocktrans %}"]|Acc]);
+unparse([{'blocktrans', Args, Contents}|Rest], Acc) ->
+    unparse(Rest, [["{% blocktrans ", unparse_args(Args), " %}", unparse(Contents), "{% endblocktrans %}"]|Acc]);
+unparse([{'call', Identifier}|Rest], Acc) ->
+    unparse(Rest, [["{% call ", unparse_identifier(Identifier), " %}"]|Acc]);
+unparse([{'call', Identifier, With}|Rest], Acc) ->
+    unparse(Rest, [["{% call ", unparse_identifier(Identifier), " with ", unparse_args(With), " %}"]|Acc]);
+unparse([{'comment', Contents}|Rest], Acc) ->
+    unparse(Rest, [["{% comment %}", unparse(Contents), "{% endcomment %}"]|Acc]);
+unparse([{'cycle', Names}|Rest], Acc) ->
+    unparse(Rest, [["{% cycle ", unparse(Names), " %}"]|Acc]);
+unparse([{'cycle_compat', Names}|Rest], Acc) ->
+    unparse(Rest, [["{% cycle ", unparse_cycle_compat_names(Names), " %}"]|Acc]);
+unparse([{'date', 'now', Value}|Rest], Acc) ->
+    unparse(Rest, [["{% now ", unparse_value(Value), " %}"]|Acc]);
+unparse([{'filter', FilterList, Contents}|Rest], Acc) ->
+    unparse(Rest, [["{% filter ", unparse_filters(FilterList), " %}", unparse(Contents), "{% endfilter %}"]|Acc]);
+unparse([{'firstof', Vars}|Rest], Acc) ->
+    unparse(Rest, [["{% firstof ", unparse(Vars), " %}"]|Acc]);
+unparse([{'for', {'in', IteratorList, Identifier}, Contents}|Rest], Acc) ->
+    unparse(Rest, [["{% for ", unparse_identifier(Identifier), " in ", unparse(IteratorList), " %}", 
+                unparse(Contents), 
+                "{% endfor %}"]|Acc]);
+unparse([{'for', {'in', IteratorList, Identifier}, Contents, EmptyPartsContents}|Rest], Acc) ->
+    unparse(Rest, [["{% for ", unparse_identifier(Identifier), " in ", unparse(IteratorList), " %}",
+                unparse(Contents),
+                "{% empty %}",
+                unparse(EmptyPartsContents),
+                "{% endfor %}"]|Acc]);
+unparse([{'if', Expression, Contents}|Rest], Acc) ->
+    unparse(Rest, [["{% if ", unparse_expression(Expression), " %}",
+                unparse(Contents),
+                "{% endif %}"]|Acc]);
+unparse([{'ifelse', Expression, IfContents, ElseContents}|Rest], Acc) ->
+    unparse(Rest, [["{% if ", unparse_expression(Expression), " %}",
+                unparse(IfContents),
+                "{% else %}",
+                unparse(ElseContents),
+                "{% endif %}"]|Acc]);
+unparse([{'ifequal', [Arg1, Arg2], Contents}|Rest], Acc) ->
+    unparse(Rest, [["{% ifequal ", unparse_value(Arg1), " ", unparse_value(Arg2), " %}",
+                unparse(Contents),
+                "{% endifequal %}"]|Acc]);
+unparse([{'ifequalelse', [Arg1, Arg2], IfContents, ElseContents}|Rest], Acc) ->
+    unparse(Rest, [["{% ifequal ", unparse_value(Arg1), " ", unparse_value(Arg2), " %}",
+                unparse(IfContents),
+                "{% else %}",
+                unparse(ElseContents),
+                "{% endifequal %}"]|Acc]);
+unparse([{'ifnotequal', [Arg1, Arg2], Contents}|Rest], Acc) ->
+    unparse(Rest, [["{% ifnotequal ", unparse_value(Arg1), " ", unparse_value(Arg2), " %}",
+                unparse(Contents),
+                "{% endifnotequal %}"]|Acc]);
+unparse([{'ifnotequalelse', [Arg1, Arg2], IfContents, ElseContents}|Rest], Acc) ->
+    unparse(Rest, [["{% ifnotequal ", unparse_value(Arg1), " ", unparse_value(Arg2), " %}",
+                unparse(IfContents),
+                "{% else %}",
+                unparse(ElseContents),
+                "{% endifnotequal %}"]|Acc]);
+unparse([{'include', Value, []}|Rest], Acc) ->
+    unparse(Rest, [["{% include ", unparse_value(Value), " %}"]|Acc]);
+unparse([{'include', Value, Args}|Rest], Acc) ->
+    unparse(Rest, [["{% include ", unparse_value(Value), " with ", unparse_args(Args)]|Acc]);
+unparse([{'include_only', Value, []}|Rest], Acc) ->
+    unparse(Rest, [["{% include ", unparse_value(Value), " only %}"]|Acc]);
+unparse([{'include_only', Value, Args}|Rest], Acc) ->
+    unparse(Rest, [["{% include ", unparse_value(Value), " with ", unparse_args(Args), " only %}"]|Acc]);
+unparse([{'spaceless', Contents}|Rest], Acc) ->
+    unparse(Rest, [["{% spaceless %}", unparse(Contents), "{% endspaceless %}"]|Acc]);
+unparse([{'ssi', Arg}|Rest], Acc) ->
+    unparse(Rest, [["{% ssi ", unparse_value(Arg), " %}"]|Acc]);
+unparse([{'ssi_parsed', Arg}|Rest], Acc) ->
+    unparse(Rest, [["{% ssi ", unparse_value(Arg), " parsed %}"]|Acc]);
+unparse([{'string', _, String}|Rest], Acc) ->
+    unparse(Rest, [[String]|Acc]);
+unparse([{'tag', Identifier, []}|Rest], Acc) ->
+    unparse(Rest, [["{% ", unparse_identifier(Identifier), " %}"]|Acc]);
+unparse([{'tag', Identifier, Args}|Rest], Acc) ->
+    unparse(Rest, [["{% ", unparse_identifier(Identifier), " ", unparse_args(Args), " %}"]|Acc]);
+unparse([{'templatetag', Identifier}|Rest], Acc) ->
+    unparse(Rest, [["{% templatetag ", unparse_identifier(Identifier), " %}"]|Acc]);
+unparse([{'trans', Value}|Rest], Acc) ->
+    unparse(Rest, [["{% trans ", unparse_value(Value), " %}"]|Acc]);
+unparse([{'widthratio', Numerator, Denominator, Scale}|Rest], Acc) ->
+    unparse(Rest, [["{% widthratio ", unparse_value(Numerator), " ", unparse_value(Denominator), " ", unparse_value(Scale), " %}"]|Acc]);
+unparse([{'with', Args, Contents}|Rest], Acc) ->
+    unparse(Rest, [["{% with ", unparse_args(Args), " %}",
+                unparse(Contents),
+                "{% endwidth %}"]|Acc]);
+unparse([ValueToken|Rest], Acc) ->
+    unparse(Rest, [["{{ ", unparse_value(ValueToken), " }}"]|Acc]).
+
+
+unparse_identifier({identifier, _, Name}) ->
+    atom_to_list(Name).
+
+unparse_filters(FilterList) ->
+    unparse_filters(FilterList, []).
+
+unparse_filters([], Acc) ->
+    lists:reverse(Acc);
+unparse_filters([Filter], Acc) ->
+    unparse_filters([], [unparse_filter(Filter)|Acc]);
+unparse_filters([Filter|Rest], Acc) ->
+    unparse_filters(Rest, lists:reverse([unparse_filter(Filter), "|"], Acc)).
+
+unparse_filter([Identifier]) ->
+    unparse_identifier(Identifier);
+unparse_filter([Identifier, Arg]) ->
+    [unparse_identifier(Identifier), ":", unparse_value(Arg)].
+
+unparse_expression({'expr', "in", Arg1, Arg2}) ->
+    [unparse_value(Arg1), " in ", unparse_value(Arg2)];
+unparse_expression({'expr', "not", {'expr', "in", Arg1, Arg2}}) ->
+    [unparse_value(Arg1), " not in ", unparse_value(Arg2)];
+unparse_expression({'expr', "not", Expr}) ->
+    ["not ", unparse_expression(Expr)];
+unparse_expression({'expr', "eq", Arg1, Arg2}) ->
+    [unparse_value(Arg1), " == ", unparse_value(Arg2)];
+unparse_expression({'expr', "ne", Arg1, Arg2}) ->
+    [unparse_value(Arg1), " != ", unparse_value(Arg2)];
+unparse_expression({'expr', "ge", Arg1, Arg2}) ->
+    [unparse_value(Arg1), " >= ", unparse_value(Arg2)];
+unparse_expression({'expr', "le", Arg1, Arg2}) ->
+    [unparse_value(Arg1), " <= ", unparse_value(Arg2)];
+unparse_expression({'expr', "gt", Arg1, Arg2}) ->
+    [unparse_value(Arg1), " > ", unparse_value(Arg2)];
+unparse_expression({'expr', "lt", Arg1, Arg2}) ->
+    [unparse_value(Arg1), " < ", unparse_value(Arg2)];
+unparse_expression({'expr', "or", Arg1, Arg2}) ->
+    [unparse_expression(Arg1), " or ", unparse_expression(Arg2)];
+unparse_expression({'expr', "and", Arg1, Arg2}) ->
+    [unparse_expression(Arg1), " and ", unparse_expression(Arg2)];
+unparse_expression(Other) ->
+    unparse_value(Other).
+
+unparse_value({'string_literal', _, Value}) ->
+    Value;
+unparse_value({'number_literal', _, Value}) ->
+    Value;
+unparse_value({'apply_filter', Variable, Filter}) ->
+    [unparse_value(Variable), "|", unparse_filter(Filter)];
+unparse_value({'attribute', {Variable, Identifier}}) ->
+    [unparse_value(Variable), ".", unparse_identifier(Identifier)];
+unparse_value({'variable', Identifier}) ->
+    unparse_identifier(Identifier).
+
+unparse_args(Args) ->
+    unparse_args(Args, []).
+
+unparse_args([], Acc) ->
+    lists:reverse(Acc);
+unparse_args([{{identifier, _, Name}, Value}], Acc) ->
+    unparse_args([], [[atom_to_list(Name), "=", unparse_value(Value)]|Acc]);
+unparse_args([{{identifier, _, Name}, Value}|Rest], Acc) ->
+    unparse_args(Rest, lists:reverse([[atom_to_list(Name), "=", unparse_value(Value)], " "], Acc)).
+
+unparse_cycle_compat_names(Names) ->
+    unparse_cycle_compat_names(Names, []).
+
+unparse_cycle_compat_names([], Acc) ->
+    lists:reverse(Acc);
+unparse_cycle_compat_names([{identifier, _, Name}], Acc) ->
+    unparse_cycle_compat_names([], [atom_to_list(Name)|Acc]);
+unparse_cycle_compat_names([{identifier, _, Name}|Rest], Acc) ->
+    unparse_cycle_compat_names(Rest, lists:reverse([atom_to_list(Name), ", "], Acc)).

+ 48 - 0
src/i18n/blocktrans_extractor.erl

@@ -0,0 +1,48 @@
+-module(blocktrans_extractor).
+
+-export([extract/1]).
+
+extract(Path) when is_list(Path) ->
+    {ok, Contents} = file:read_file(Path),
+    extract(Contents);
+
+extract(Contents) when is_binary(Contents) ->
+    case erlydtl_compiler:parse(Contents) of
+        {ok, ParseTree} ->
+            Blocks = process_tree(ParseTree),
+            {ok, Blocks};
+        Error ->
+            Error
+    end.
+
+process_tree(ParseTree) ->
+    process_tree(ParseTree, []).
+
+process_tree([], Acc) ->
+    lists:reverse(Acc);
+process_tree([{'autoescape', _, Contents}|Rest], Acc) ->
+    process_tree(Rest, lists:reverse(process_tree(Contents), Acc));
+process_tree([{'block', _, Contents}|Rest], Acc) ->
+    process_tree(Rest, lists:reverse(process_tree(Contents), Acc));
+process_tree([{'blocktrans', _, Contents}|Rest], Acc) ->
+    process_tree(Rest, [lists:flatten(erlydtl_unparser:unparse(Contents))|Acc]); % <-- where all the action happens
+process_tree([{'filter', _, Contents}|Rest], Acc) ->
+    process_tree(Rest, lists:reverse(process_tree(Contents), Acc));
+process_tree([{'for', _, Contents}|Rest], Acc) ->
+    process_tree(Rest, lists:reverse(process_tree(Contents), Acc));
+process_tree([{'for', _, Contents, EmptyPartContents}|Rest], Acc) ->
+    process_tree(Rest, lists:reverse(process_tree(Contents) ++ process_tree(EmptyPartContents), Acc));
+process_tree([{Instruction, _, Contents}|Rest], Acc) when Instruction =:= 'if'; 
+                                                          Instruction =:= 'ifequal'; 
+                                                          Instruction =:= 'ifnotequal' ->
+    process_tree(Rest, lists:reverse(process_tree(Contents), Acc));
+process_tree([{Instruction, _, IfContents, ElseContents}|Rest], Acc) when Instruction =:= 'ifelese'; 
+                                                                          Instruction =:= 'ifequalelse'; 
+                                                                          Instruction =:= 'ifnotequalelse' ->
+    process_tree(Rest, lists:reverse(process_tree(IfContents) ++ process_tree(ElseContents), Acc));
+process_tree([{'spaceless', Contents}|Rest], Acc) ->
+    process_tree(Rest, lists:reverse(process_tree(Contents), Acc));
+process_tree([{'with', _, Contents}|Rest], Acc) ->
+    process_tree(Rest, lists:reverse(process_tree(Contents), Acc));
+process_tree([_|Rest], Acc) ->
+    process_tree(Rest, Acc).

+ 2 - 2
src/i18n/blocktrans_parser.erl

@@ -7,7 +7,7 @@ parse(Tokens) ->
 
 parse([], Acc) ->
     lists:reverse(Acc);
-parse([{open_blocktrans, _, Name}, {text, _, Text}, {close_blocktrans, _}|Rest], Acc) ->
-    parse(Rest, [{Name, unicode:characters_to_binary(Text)}|Acc]);
+parse([{open_blocktrans, _, _}, {text, _, Text}, {close_blocktrans, _}|Rest], Acc) ->
+    parse(Rest, [Text|Acc]);
 parse([{text, _, _}|Rest], Acc) ->
     parse(Rest, Acc).

+ 1 - 1
src/i18n/blocktrans_scanner.erl

@@ -137,7 +137,7 @@ append_text(" ", _Pos, [{open_blocktrans, BPos, ""}|Rest]) ->
 append_text([C], _Pos, [{open_blocktrans, BPos, Name}|Rest]) when ((C >= $a) and (C =< $z)) or ((C >= $A) and (C =< $Z)) or (C =:= $_) orelse (C >= $0 andalso C =< $9) ->
     [{open_blocktrans, BPos, [C|Name]}|Rest];
 append_text(" ", _Pos, [{open_blocktrans, BPos, Name}|Rest]) when is_list(Name) ->
-    [{open_blocktrans, BPos, list_to_atom(lists:reverse(Name))}|Rest];
+    [{open_blocktrans, BPos, lists:reverse(Name)}|Rest];
 append_text("%}", {Row, Column}, [{open_blocktrans, _BPos, _Name}|_] = Scanned) ->
     [{text, {Row, Column + 2}, ""}|Scanned];
 append_text(_Chars, _Pos, [{open_blocktrans, _BPos, _Name}|_] = Scanned) ->

+ 4 - 4
tests/src/erlydtl_unittests.erl

@@ -949,12 +949,12 @@ tests() ->
     {"blocktrans",
         [
             {"blocktrans default locale",
-                <<"{% blocktrans foo %}Hello{% endblocktrans %}">>, [], <<"Hello">>},
+                <<"{% blocktrans %}Hello{% endblocktrans %}">>, [], <<"Hello">>},
             {"blocktrans choose locale",
-                <<"{% blocktrans hello %}Hello, {{ name }}{% endblocktrans %}">>, [{name, "Mr. President"}], [{locale, "de"}],
-                [{blocktrans_locales, ["de"]}, {blocktrans_fun, fun(hello, "de") -> <<"Guten tag, {{ name }}">> end}], <<"Guten tag, Mr. President">>},
+                <<"{% blocktrans %}Hello, {{ name }}{% endblocktrans %}">>, [{name, "Mr. President"}], [{locale, "de"}],
+                [{blocktrans_locales, ["de"]}, {blocktrans_fun, fun("Hello, {{ name }}", "de") -> <<"Guten tag, {{ name }}">> end}], <<"Guten tag, Mr. President">>},
             {"blocktrans with args",
-                <<"{% blocktrans foo with var1=foo %}{{ var1 }}{% endblocktrans %}">>, [{foo, "Hello"}], <<"Hello">>}
+                <<"{% blocktrans with var1=foo %}{{ var1 }}{% endblocktrans %}">>, [{foo, "Hello"}], <<"Hello">>}
         ]},
     {"widthratio", [
             {"Literals", <<"{% widthratio 5 10 100 %}">>, [], <<"50">>},