Browse Source

date translation interface working + tests happy

oxpa 9 years ago
parent
commit
1a99256c2a
3 changed files with 144 additions and 98 deletions
  1. 21 6
      src/erlydtl_beam_compiler.erl
  2. 19 11
      src/erlydtl_filters.erl
  3. 104 81
      src/filter_lib/erlydtl_dateformat.erl

+ 21 - 6
src/erlydtl_beam_compiler.erl

@@ -1120,13 +1120,28 @@ filter_ast1({{identifier, Pos, Name}, Args}, ValueAst, TreeWalker) ->
             empty_ast(?WARN({Pos, Error}, TreeWalker1))
     end.
 
-filter_ast2(Name, Args, #dtl_context{ filters = Filters }) ->
+% special case for date, which reqires localisation
+% may be replaced later with a query to a list 
+% of functions which require translation
+filter_ast2('date' = Name, Args, #dtl_context{ filters = Filters } = Ctx) ->
+    case proplists:get_value(Name, Filters) of
+        {Mod, Fun} -> 
+            case erlang:function_exported(Mod, Fun, length(Args) + 2) of
+                true -> {ok, ?Q("'@Mod@':'@Fun@'(_@Args, _TranslationFun, _CurrentLocale )")};
+                false -> filter_ast3(Name, Args, Ctx) % redefined 'date'?
+            end;
+        % should never happen
+        undefined -> {unknown_filter, Name, length(Args)}
+    end;
+filter_ast2(Name, Args, Ctx) ->
+    filter_ast3(Name, Args, Ctx).
+
+filter_ast3(Name, Args, #dtl_context{ filters = Filters }) ->
     case proplists:get_value(Name, Filters) of
         {Mod, Fun}=Filter ->
-            case {erlang:function_exported(Mod, Fun, length(Args)), Name} of
-                {true, 'date'} -> {ok, ?Q("'@Mod@':'@Fun@'(_@Args, _TranslationFun)")};
-                {true, _} -> {ok, ?Q("'@Mod@':'@Fun@'(_@Args)")};
-                {false, _} -> {filter_args, Name, Filter, length(Args)}
+            case erlang:function_exported(Mod, Fun, length(Args)) of
+                true -> {ok, ?Q("'@Mod@':'@Fun@'(_@Args)")};
+                false -> {filter_args, Name, Filter, length(Args)}
             end;
         undefined ->
             {unknown_filter, Name, length(Args)}
@@ -1483,7 +1498,7 @@ now_ast(FormatString, TreeWalker) ->
     %% i.e. \"foo\" becomes "foo"
     UnescapeOuter = string:strip(FormatString, both, 34),
     {{StringAst, Info}, TreeWalker1} = string_ast(UnescapeOuter, TreeWalker),
-    {{?Q("erlydtl_dateformat:format(_@StringAst, _TranslationFun)"), Info}, TreeWalker1}.
+    {{?Q("erlydtl_dateformat:format(_@StringAst, _TranslationFun, _CurrentLocale)"), Info}, TreeWalker1}.
 
 spaceless_ast(Contents, TreeWalker) ->
     {{Ast, Info}, TreeWalker1} = body_ast(Contents, TreeWalker),

+ 19 - 11
src/erlydtl_filters.erl

@@ -57,6 +57,7 @@
         date/1,
         date/2,
         date/3,
+        date/4,
         default/2,
         default_if_none/2,
         dictsort/2,
@@ -235,25 +236,32 @@ cut(Input, Arg) when is_binary(Input) ->
 cut(Input, [Char]) when is_list(Input) ->
     cut(Input, Char, []).
  
-%% @doc Formats a date according to the default format.
-date(Input) ->
-    date(Input, fun(A) -> A end).
-
 %% @doc Formats a date according to the default format. 
-%% @doc Translating tokens with function F
-date(Input, F) when is_function(F)->
-    date(Input, "F j, Y", F).
+date(Input) ->
+    date(Input, "F j, Y").
 
 %% @doc Formats a date according to the given format.
-date(Input, FormatStr, F)
+date(Input, FormatStr)
   when is_tuple(Input)
-       andalso (size(Input) == 2 orelse size(Input) == 3)
-       andalso is_function(F) ->
+       andalso (size(Input) == 2 orelse size(Input) == 3) ->
     erlydtl_dateformat:format(Input, FormatStr);
-date(Input, _FormatStr, _F) ->
+date(Input, _FormatStr) ->
+    io:format("Unexpected date parameter: ~p~n", [Input]),
+    "".
+
+%% @doc Formats a date according to the default format 
+%%      localizing it with provided translation function.
+date(Input, TransFun, Locale) ->
+    date(Input, "F j, Y", TransFun, Locale).
+date(Input, FormatStr, TransFun, Locale)
+  when is_tuple(Input)
+       andalso (size(Input) == 2 orelse size(Input) == 3) ->
+    erlydtl_dateformat:format(Input, FormatStr, TransFun, Locale);
+date(Input, _FormatStr, _TransFun, _Locale) ->
     io:format("Unexpected date parameter: ~p~n", [Input]),
     "".
 
+
 %% @doc If value evaluates to `false', use given default. Otherwise, use the value.
 default(Input, Default) ->
     case erlydtl_runtime:is_false(Input) of

+ 104 - 81
src/filter_lib/erlydtl_dateformat.erl

@@ -1,5 +1,5 @@
 -module(erlydtl_dateformat).
--export([format/1, format/2, format/3]).
+-export([format/1, format/2, format/3, format/4]).
 
 -define(TAG_SUPPORTED(C),
         C =:= $a orelse
@@ -9,6 +9,7 @@
         C =:= $c orelse
         C =:= $d orelse
         C =:= $D orelse
+        C =:= $E orelse
         C =:= $f orelse
         C =:= $F orelse
         C =:= $g orelse
@@ -45,44 +46,60 @@
 %% Format the current date/time
 %%
 format(FormatString) when is_binary(FormatString) ->
-    format(binary_to_list(FormatString));
+    format(binary_to_list(FormatString), fun stub_tran/2, <<>>);
 format(FormatString) ->
     {Date, Time} = erlang:localtime(),
-    replace_tags(Date, Time, FormatString).
+    replace_tags(Date, Time, FormatString, fun stub_tran/2, <<>>).
+
 %%
 %% Format a tuple of the form {{Y,M,D},{H,M,S}}
 %% This is the format returned by erlang:localtime()
 %% and other standard date/time BIFs
 %%
 format(DateTime, FormatString) when is_binary(FormatString) ->
-    format(DateTime, binary_to_list(FormatString));
+    format(DateTime, binary_to_list(FormatString),fun stub_tran/2, <<>>);
 format({{_,_,_} = Date,{_,_,_} = Time}, FormatString) ->
-    replace_tags(Date, Time, FormatString);
+    replace_tags(Date, Time, FormatString, fun stub_tran/2, <<>> );
 %%
 %% Format a tuple of the form {Y,M,D}
 %%
 format({_,_,_} = Date, FormatString) ->
-    replace_tags(Date, {0,0,0}, FormatString);
+    replace_tags(Date, {0,0,0}, FormatString, fun stub_tran/2, <<>> );
 format(DateTime, FormatString) ->
     io:format("Unrecognised date paramater : ~p~n", [DateTime]),
     FormatString.
-format(DateTime, FormatString, TransFun) ->
-    io:format("Translations are not yet supported", []),
+
+%% The same set of functions with TranslationFunction and Locale args
+format(FormatString, TransFun, Locale) when is_binary(FormatString) ->
+    format(binary_to_list(FormatString), TransFun, Locale);
+format(FormatString, TransFun, Locale) ->
+    {Date, Time} = erlang:localtime(),
+    replace_tags(Date, Time, FormatString, TransFun, Locale).
+
+format(DateTime, FormatString, TransFun, Locale) when is_binary(FormatString) ->
+    format(DateTime, binary_to_list(FormatString), TransFun, Locale);
+format({{_,_,_} = Date,{_,_,_} = Time}, FormatString, TransFun, Locale) ->
+    replace_tags(Date, Time, FormatString, TransFun, Locale );
+
+format({_,_,_} = Date, FormatString, TransFun, Locale) ->
+    replace_tags(Date, {0,0,0}, FormatString, TransFun, Locale );
+format(DateTime, FormatString, _TransFun, _Locale) ->
+    io:format("Unrecognised date paramater : ~p~n", [DateTime]),
     FormatString.
 
-replace_tags(Date, Time, Input) ->
-    replace_tags(Date, Time, Input, [], noslash).
-replace_tags(_Date, _Time, [], Out, _State) ->
+replace_tags(Date, Time, Input, TransFun, Locale) ->
+    replace_tags(Date, Time, Input, [], noslash, TransFun, Locale).
+replace_tags(_Date, _Time, [], Out, _State, _TransFun, _Locale) ->
     lists:reverse(Out);
-replace_tags(Date, Time, [C|Rest], Out, noslash) when ?TAG_SUPPORTED(C) ->
+replace_tags(Date, Time, [C|Rest], Out, noslash, TransFun, Locale) when ?TAG_SUPPORTED(C) ->
     replace_tags(Date, Time, Rest,
-                 lists:reverse(tag_to_value(C, Date, Time)) ++ Out, noslash);
-replace_tags(Date, Time, [$\\|Rest], Out, noslash) ->
-    replace_tags(Date, Time, Rest, Out, slash);
-replace_tags(Date, Time, [C|Rest], Out, slash) ->
-    replace_tags(Date, Time, Rest, [C|Out], noslash);
-replace_tags(Date, Time, [C|Rest], Out, _State) ->
-    replace_tags(Date, Time, Rest, [C|Out], noslash).
+                 lists:reverse(tag_to_value(C, Date, Time, TransFun, Locale)) ++ Out, noslash, TransFun, Locale);
+replace_tags(Date, Time, [$\\|Rest], Out, noslash, TransFun, Locale) ->
+    replace_tags(Date, Time, Rest, Out, slash, TransFun, Locale);
+replace_tags(Date, Time, [C|Rest], Out, slash, TransFun, Locale) ->
+    replace_tags(Date, Time, Rest, [C|Out], noslash, TransFun, Locale);
+replace_tags(Date, Time, [C|Rest], Out, _State, TransFun, Locale) ->
+    replace_tags(Date, Time, Rest, [C|Out], noslash, TransFun, Locale).
 
 
 %%-----------------------------------------------------------
@@ -90,25 +107,25 @@ replace_tags(Date, Time, [C|Rest], Out, _State) ->
 %%-----------------------------------------------------------
 
 %% 'a.m.' or 'p.m.'
-tag_to_value($a, _, {H, _, _}) when H > 11 -> "p.m.";
-tag_to_value($a, _, _) -> "a.m.";
+tag_to_value($a, _, {H, _, _}, TransFun, Locale) when H > 11 -> "p.m.";
+tag_to_value($a, _, _, TransFun, Locale) -> "a.m.";
 
 %% 'AM' or 'PM'
-tag_to_value($A, _, {H, _, _}) when H > 11 -> "PM";
-tag_to_value($A, _, _) -> "AM";
+tag_to_value($A, _, {H, _, _}, TransFun, Locale) when H > 11 -> "PM";
+tag_to_value($A, _, _, TransFun, Locale) -> "AM";
 
 %% Swatch Internet time
-tag_to_value($B, _, _) ->
+tag_to_value($B, _, _, TransFun, Locale) ->
     ""; %% NotImplementedError
 
 %% ISO 8601 Format.
-tag_to_value($c, Date, Time) ->
-    tag_to_value($Y, Date, Time) ++
-        "-" ++ tag_to_value($m, Date, Time) ++
-        "-" ++ tag_to_value($d, Date, Time) ++
-        "T" ++ tag_to_value($H, Date, Time) ++
-        ":" ++ tag_to_value($i, Date, Time) ++
-        ":" ++ tag_to_value($s, Date, Time);
+tag_to_value($c, Date, Time, TransFun, Locale) ->
+    tag_to_value($Y, Date, Time, TransFun, Locale) ++
+        "-" ++ tag_to_value($m, Date, Time, TransFun, Locale) ++
+        "-" ++ tag_to_value($d, Date, Time, TransFun, Locale) ++
+        "T" ++ tag_to_value($H, Date, Time, TransFun, Locale) ++
+        ":" ++ tag_to_value($i, Date, Time, TransFun, Locale) ++
+        ":" ++ tag_to_value($s, Date, Time, TransFun, Locale);
 
 %%
 %% Time, in 12-hour hours and minutes, with minutes
@@ -118,46 +135,46 @@ tag_to_value($c, Date, Time) ->
 %%
 %% Proprietary extension.
 %%
-tag_to_value($f, Date, {H, 0, S}) ->
+tag_to_value($f, Date, {H, 0, S}, TransFun, Locale) ->
     %% If min is zero then return the hour only
-    tag_to_value($g, Date, {H, 0, S});
-tag_to_value($f, Date, Time) ->
+    tag_to_value($g, Date, {H, 0, S}, TransFun, Locale);
+tag_to_value($f, Date, Time, TransFun, Locale) ->
     %% Otherwise return hours and mins
-    tag_to_value($g, Date, Time)
-        ++ ":" ++ tag_to_value($i, Date, Time);
+    tag_to_value($g, Date, Time, TransFun, Locale)
+        ++ ":" ++ tag_to_value($i, Date, Time, TransFun, Locale);
 
 %% Hour, 12-hour format without leading zeros; i.e. '1' to '12'
-tag_to_value($g, _, {H,_,_}) ->
+tag_to_value($g, _, {H,_,_}, TransFun, Locale) ->
     integer_to_list(hour_24to12(H));
 
 %% Hour, 24-hour format without leading zeros; i.e. '0' to '23'
-tag_to_value($G, _, {H,_,_}) ->
+tag_to_value($G, _, {H,_,_}, TransFun, Locale) ->
     integer_to_list(H);
 
 %% Hour, 12-hour format; i.e. '01' to '12'
-tag_to_value($h, _, {H,_,_}) ->
+tag_to_value($h, _, {H,_,_}, TransFun, Locale) ->
     integer_to_list_zerofill(hour_24to12(H));
 
 %% Hour, 24-hour format; i.e. '00' to '23'
-tag_to_value($H, _, {H,_,_}) ->
+tag_to_value($H, _, {H,_,_}, TransFun, Locale) ->
     integer_to_list_zerofill(H);
 
 %% Minutes; i.e. '00' to '59'
-tag_to_value($i, _, {_,M,_}) ->
+tag_to_value($i, _, {_,M,_}, TransFun, Locale) ->
     integer_to_list_zerofill(M);
 
 %% Time, in 12-hour hours, minutes and 'a.m.'/'p.m.', with minutes left off
 %% if they're zero and the strings 'midnight' and 'noon' if appropriate.
 %% Examples: '1 a.m.', '1:30 p.m.', 'midnight', 'noon', '12:30 p.m.'
 %% Proprietary extension.
-tag_to_value($P, _, {0,  0, _}) -> "midnight";
-tag_to_value($P, _, {12, 0, _}) -> "noon";
-tag_to_value($P, Date, Time) ->
-    tag_to_value($f, Date, Time)
-        ++ " " ++ tag_to_value($a, Date, Time);
+tag_to_value($P, _, {0,  0, _}, TransFun, Locale) -> "midnight";
+tag_to_value($P, _, {12, 0, _}, TransFun, Locale) -> "noon";
+tag_to_value($P, Date, Time, TransFun, Locale) ->
+    tag_to_value($f, Date, Time, TransFun, Locale)
+        ++ " " ++ tag_to_value($a, Date, Time, TransFun, Locale);
 
 %% Seconds; i.e. '00' to '59'
-tag_to_value($s, _, {_,_,S}) ->
+tag_to_value($s, _, {_,_,S}, TransFun, Locale) ->
     integer_to_list_zerofill(S);
 
 %%-----------------------------------------------------------
@@ -165,67 +182,72 @@ tag_to_value($s, _, {_,_,S}) ->
 %%-----------------------------------------------------------
 
 %% Month, textual, 3 letters, lowercase; e.g. 'jan'
-tag_to_value($b, {_,M,_}, _) ->
+tag_to_value($b, {_,M,_}, _, TransFun, Locale) ->
     string:sub_string(monthname(M), 1, 3);
 
 %% Day of the month, 2 digits with leading zeros; i.e. '01' to '31'
-tag_to_value($d, {_, _, D}, _) ->
+tag_to_value($d, {_, _, D}, _, TransFun, Locale) ->
     integer_to_list_zerofill(D);
 
 %% Day of the week, textual, 3 letters; e.g. 'Fri'
-tag_to_value($D, Date, _) ->
+tag_to_value($D, Date, _, TransFun, Locale) ->
     Dow = calendar:day_of_the_week(Date),
     ucfirst(string:sub_string(dayname(Dow), 1, 3));
 
+%% Month, textual, long, alternative; e.g. 'Listopada'
+tag_to_value($F, {_,M,_}, _, TransFun, Locale) ->
+    ucfirst(monthname(M));
+
+
 %% Month, textual, long; e.g. 'January'
-tag_to_value($F, {_,M,_}, _) ->
+tag_to_value($F, {_,M,_}, _, TransFun, Locale) ->
     ucfirst(monthname(M));
 
 %% '1' if Daylight Savings Time, '0' otherwise.
-tag_to_value($I, _, _) ->
+tag_to_value($I, _, _, TransFun, Locale) ->
     "TODO";
 
 %% Day of the month without leading zeros; i.e. '1' to '31'
-tag_to_value($j, {_, _, D}, _) ->
+tag_to_value($j, {_, _, D}, _, TransFun, Locale) ->
     integer_to_list(D);
 
 %% Day of the week, textual, long; e.g. 'Friday'
-tag_to_value($l, Date, _) ->
+tag_to_value($l, Date, _, TransFun, Locale) ->
     ucfirst(dayname(calendar:day_of_the_week(Date)));
 
 %% Boolean for whether it is a leap year; i.e. True or False
-tag_to_value($L, {Y,_,_}, _) ->
+tag_to_value($L, {Y,_,_}, _, TransFun, Locale) ->
     case calendar:is_leap_year(Y) of
         true -> "True";
         _ -> "False"
     end;
 
 %% Month; i.e. '01' to '12'
-tag_to_value($m, {_, M, _}, _) ->
+tag_to_value($m, {_, M, _}, _, TransFun, Locale) ->
     integer_to_list_zerofill(M);
 
 %% Month, textual, 3 letters; e.g. 'Jan'
-tag_to_value($M, {_,M,_}, _) ->
+tag_to_value($M, {_,M,_}, _, TransFun, Locale) ->
     ucfirst(string:sub_string(monthname(M), 1, 3));
 
 %% Month without leading zeros; i.e. '1' to '12'
-tag_to_value($n, {_, M, _}, _) ->
+tag_to_value($n, {_, M, _}, _, TransFun, Locale) ->
     integer_to_list(M);
 
 %% Month abbreviation in Associated Press style. Proprietary extension.
-tag_to_value($N, {_,M,_}, _) when M =:= 9 ->
+tag_to_value($N, {_,M,_}, _, TransFun, Locale) when M =:= 9 ->
     %% Special case - "Sept."
     ucfirst(string:sub_string(monthname(M), 1, 4)) ++ ".";
-tag_to_value($N, {_,M,_}, _) when M < 3 orelse M > 7 ->
+tag_to_value($N, {_,M,_}, _, TransFun, Locale) when M < 3 orelse M > 7 ->
     %% Jan, Feb, Aug, Oct, Nov, Dec are all
     %% abbreviated with a full-stop appended.
     ucfirst(string:sub_string(monthname(M), 1, 3)) ++ ".";
-tag_to_value($N, {_,M,_}, _) ->
+tag_to_value($N, {_,M,_}, _, TransFun, Locale) ->
     %% The rest are the fullname.
     ucfirst(monthname(M));
 
 %% Difference to Greenwich time in hours; e.g. '+0200'
-tag_to_value($O, Date, Time) ->
+tag_to_value($O, Date, Time, TransFun, Locale) ->
     Diff = utc_diff(Date, Time),
     Offset = if
                  Diff < 0 ->
@@ -236,67 +258,68 @@ tag_to_value($O, Date, Time) ->
     lists:flatten(Offset);
 
 %% RFC 2822 formatted date; e.g. 'Thu, 21 Dec 2000 16:01:07 +0200'
-tag_to_value($r, Date, Time) ->
-    replace_tags(Date, Time, "D, j M Y H:i:s O");
+tag_to_value($r, Date, Time, TransFun, Locale) ->
+    % afaik, date should not be translated in case RFC format is specified.
+    replace_tags(Date, Time, "D, j M Y H:i:s O", fun stub_tran/2, <<>> );
 
 %% English ordinal suffix for the day of the month, 2 characters;
 %% i.e. 'st', 'nd', 'rd' or 'th'
-tag_to_value($S, {_, _, D}, _) when
+tag_to_value($S, {_, _, D}, _, TransFun, Locale) when
       D rem 100 =:= 11 orelse
       D rem 100 =:= 12 orelse
       D rem 100 =:= 13 -> "th";
-tag_to_value($S, {_, _, D}, _) when D rem 10 =:= 1 -> "st";
-tag_to_value($S, {_, _, D}, _) when D rem 10 =:= 2 -> "nd";
-tag_to_value($S, {_, _, D}, _) when D rem 10 =:= 3 -> "rd";
-tag_to_value($S, _, _) -> "th";
+tag_to_value($S, {_, _, D}, _, TransFun, Locale) when D rem 10 =:= 1 -> "st";
+tag_to_value($S, {_, _, D}, _, TransFun, Locale) when D rem 10 =:= 2 -> "nd";
+tag_to_value($S, {_, _, D}, _, TransFun, Locale) when D rem 10 =:= 3 -> "rd";
+tag_to_value($S, _, _, TransFun, Locale) -> "th";
 
 %% Number of days in the given month; i.e. '28' to '31'
-tag_to_value($t, {Y,M,_}, _) ->
+tag_to_value($t, {Y,M,_}, _, TransFun, Locale) ->
     integer_to_list(calendar:last_day_of_the_month(Y,M));
 
 %% Time zone of this machine; e.g. 'EST' or 'MDT'
-tag_to_value($T, _, _) ->
+tag_to_value($T, _, _, TransFun, Locale) ->
     "TODO";
 
 %% Seconds since the Unix epoch (January 1 1970 00:00:00 GMT)
-tag_to_value($U, Date, Time) ->
+tag_to_value($U, Date, Time, TransFun, Locale) ->
     EpochSecs = calendar:datetime_to_gregorian_seconds({Date, Time})
         - calendar:datetime_to_gregorian_seconds({{1970,1,1},{0,0,0}}),
     integer_to_list(EpochSecs);
 
 %% Day of the week, numeric, i.e. '0' (Sunday) to '6' (Saturday)
-tag_to_value($w, Date, _) ->
+tag_to_value($w, Date, _, TransFun, Locale) ->
     %% Note: calendar:day_of_the_week returns
     %%   1 | .. | 7. Monday = 1, Tuesday = 2, ..., Sunday = 7
     integer_to_list(calendar:day_of_the_week(Date) rem 7);
 
 %% ISO-8601 week number of year, weeks starting on Monday
-tag_to_value($W, {Y,M,D}, _) ->
+tag_to_value($W, {Y,M,D}, _, TransFun, Locale) ->
     integer_to_list(year_weeknum(Y,M,D));
 
 %% Year, 2 digits; e.g. '99'
-tag_to_value($y, {Y, _, _}, _) ->
+tag_to_value($y, {Y, _, _}, _, TransFun, Locale) ->
     string:sub_string(integer_to_list(Y), 3);
 
 %% Year, 4 digits; e.g. '1999'
-tag_to_value($Y, {Y, _, _}, _) ->
+tag_to_value($Y, {Y, _, _}, _, TransFun, Locale) ->
     integer_to_list(Y);
 
 %% Day of the year; i.e. '0' to '365'
-tag_to_value($z, {Y,M,D}, _) ->
+tag_to_value($z, {Y,M,D}, _, TransFun, Locale) ->
     integer_to_list(day_of_year(Y,M,D));
 
 %% Time zone offset in seconds (i.e. '-43200' to '43200'). The offset for
 %% timezones west of UTC is always negative, and for those east of UTC is
 %% always positive.
-tag_to_value($Z, _, _) ->
+tag_to_value($Z, _, _, TransFun, Locale) ->
     "TODO";
 
 %% o – the ISO 8601 year number
-tag_to_value($o, {Y,M,D}, _) ->
+tag_to_value($o, {Y,M,D}, _, TransFun, Locale) ->
     integer_to_list(weeknum_year(Y,M,D));
 
-tag_to_value(C, Date, Time) ->
+tag_to_value(C, Date, Time, TransFun, Locale) ->
     io:format("Unimplemented tag : ~p [Date : ~p] [Time : ~p]",
               [C, Date, Time]),
     "".
@@ -387,4 +410,4 @@ ucfirst([First | Rest]) when First >= $a, First =< $z ->
 ucfirst(Other) ->
     Other.
 
-
+stub_tran(A,_) -> A.