PageRenderTime 72ms CodeModel.GetById 14ms app.highlight 53ms RepoModel.GetById 1ms app.codeStats 0ms

/modules/mod_translation/mod_translation.erl

https://code.google.com/p/zotonic/
Erlang | 312 lines | 236 code | 39 blank | 37 comment | 6 complexity | cf8fd7b2fd94e69c1606f13d0e839afa MD5 | raw file
  1%% @author Marc Worrell <marc@worrell.nl>
  2%% @copyright 2010 Marc Worrell
  3%% Date: 2010-05-19
  4%% @doc Translation support for i18n.  Generates .po files by scanning templates.
  5
  6%% Copyright 2010 Marc Worrell
  7%%
  8%% Licensed under the Apache License, Version 2.0 (the "License");
  9%% you may not use this file except in compliance with the License.
 10%% You may obtain a copy of the License at
 11%% 
 12%%     http://www.apache.org/licenses/LICENSE-2.0
 13%% 
 14%% Unless required by applicable law or agreed to in writing, software
 15%% distributed under the License is distributed on an "AS IS" BASIS,
 16%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 17%% See the License for the specific language governing permissions and
 18%% limitations under the License.
 19
 20-module(mod_translation).
 21-author("Marc Worrell <marc@worrell.nl>").
 22
 23-mod_title("Translation").
 24-mod_description("Generate .po files containing translatable texts by scanning templates.").
 25-mod_prio(500).
 26
 27-export([
 28    observe_session_init_fold/3,
 29    observe_session_context/3,
 30    observe_auth_logon/3,
 31    
 32    init/1, 
 33    event/2,
 34    generate/1,
 35    
 36    do_choose/2
 37]).
 38
 39-include("zotonic.hrl").
 40
 41
 42%% @doc Make sure that we have the i18n.language_list setting when the site starts up.
 43init(Context) ->
 44    case Context#context.host of
 45        zotonic_status -> 
 46            ok;
 47        _Other ->
 48            case m_config:get(i18n, language_list, Context) of
 49                undefined ->
 50                    m_config:set_prop(i18n, language_list, list, [
 51                            {en, [ {language, <<"English">>}, {is_enabled, true}]},
 52                            {es, [ {language, <<"Espańol">>}, {is_enabled, true}]},
 53                            {fr, [ {language, <<"Français">>}, {is_enabled, true}]},
 54                            {nl, [ {language, <<"Nederlands">>}, {is_enabled, true}]},
 55                            {tr, [ {language, <<"Türkçe">>}, {is_enabled, true}]}
 56                        ], Context);
 57                _Exists ->
 58                    ok
 59            end
 60    end.
 61
 62
 63%% @doc Check if the user has a prefered language (in the user's persistent data). If not
 64%%      then check the accept-language header (if any) against the available languages.
 65observe_session_init_fold(session_init_fold, Context, _Context) ->
 66    case z_context:get_persistent(language, Context) of
 67        undefined ->
 68            case z_context:get_req_header("accept-language", Context) of
 69                undefined ->
 70                    Context;
 71                AcceptLanguage ->
 72                    LanguagesAvailable = [ atom_to_list(Lang)
 73                                            || {Lang, Props} <- get_language_config(Context), 
 74                                               proplists:get_value(is_enabled, Props) =:= true 
 75                                         ],
 76                    case catch do_choose(LanguagesAvailable, AcceptLanguage) of
 77                        Lang when is_list(Lang) ->
 78                            do_set_language(list_to_atom(Lang), Context);
 79                        none ->
 80                            Context;
 81                        _Error ->
 82                            % @todo log the error, might be a problem in the accept-language header
 83                            Context
 84                    end
 85            end;
 86        Language ->
 87            do_set_language(Language, Context)
 88    end.
 89
 90
 91observe_session_context(session_context, Context, _Context) ->
 92    case z_context:get_session(language, Context) of
 93        undefined -> Context;
 94        Language -> Context#context{language=Language}
 95    end.
 96
 97observe_auth_logon(auth_logon, Context, _Context) ->
 98    UserId = z_acl:user(Context),
 99    case m_rsc:p_no_acl(UserId, pref_language, Context) of
100        undefined ->
101            % Ensure that the user has a default language
102            catch m_rsc:update(UserId, [{pref_language, z_context:language(Context)}], Context),
103            Context;
104        Code -> 
105            % Switch the session to the default language of the user
106            List = get_language_config(Context),
107            Context1 = set_language(z_convert:to_list(Code), List, Context),
108            z_context:set_persistent(language, z_context:language(Context1), Context1),
109            Context1
110    end.
111
112%% @doc Set the current session (and user) language, reload the user agent's page.
113event({postback, {set_language, Args}, _TriggerId, _TargetId}, Context) ->
114    Code = case proplists:get_value(code, Args) of
115                undefined -> z_context:get_q("triggervalue", Context);
116                ArgCode -> ArgCode
117            end,
118    List = get_language_config(Context),
119    Context1 = set_language(z_convert:to_list(Code), List, Context),
120    z_context:set_persistent(language, z_context:language(Context1), Context1),
121    case z_acl:user(Context1) of
122        undefined -> 
123            nop;
124        UserId ->
125            case m_rsc:p_no_acl(UserId, pref_language, Context1) of
126                Code -> nop;
127                _ -> catch m_rsc:update(UserId, [{pref_language, Code}], Context1)
128            end
129    end,
130    z_render:wire({reload, []}, Context1);
131
132%% @doc Set the default language.
133event({postback, {language_default, Args}, _TriggerId, _TargetId}, Context) ->
134    case z_acl:is_allowed(use, ?MODULE, Context) of
135        true ->
136            {code, Code} = proplists:lookup(code, Args),
137            m_config:set_value(i18n, language, z_convert:to_binary(Code), Context),
138            Context;
139        false ->
140            z_render:growl_error("Sorry, you don't have permission to set the default language.", Context)
141    end;
142
143%% @doc Start rescanning all templates for translation tags.
144event({postback, translation_generate, _TriggerId, _TargetId}, Context) ->
145    case z_acl:is_allowed(use, ?MODULE, Context) of
146        true ->
147            spawn(fun() -> generate(Context) end),
148            z_render:growl("Started building the .po templates. This may take a while.", Context);
149        false ->
150            z_render:growl_error("Sorry, you don't have permission to scan for translations.", Context)
151    end;
152event({postback, translation_reload, _TriggerId, _TargetId}, Context) ->
153    case z_acl:is_allowed(use, ?MODULE, Context) of
154        true ->
155            spawn(fun() -> z_trans_server:load_translations(Context) end),
156            z_render:growl("Reloading all .po template in the background.", Context);
157        false ->
158            z_render:growl_error("Sorry, you don't have permission to reload translations.", Context)
159    end;
160
161event({submit, {language_edit, Args}, _TriggerId, _TargetId}, Context) ->
162    case z_acl:is_allowed(use, ?MODULE, Context) of
163        true ->
164            OldCode = proplists:get_value(code, Args, '$empty'),
165            language_add(OldCode, z_context:get_q("code", Context), z_context:get_q("language", Context), 
166                        z_context:get_q("is_enabled", Context), Context),
167            Context1 = z_render:dialog_close(Context),
168            z_render:wire({reload, []}, Context1);
169        false ->
170            z_render:growl_error("Sorry, you don't have permission to change the language list.", Context)
171    end;
172
173event({postback, {language_delete, Args}, _TriggerId, _TargetId}, Context) ->
174    case z_acl:is_allowed(use, ?MODULE, Context) of
175        true ->
176            {code, Code} = proplists:lookup(code, Args),
177            language_delete(Code, Context),
178            Context1 = z_render:dialog_close(Context),
179            z_render:wire({reload, []}, Context1);
180        false ->
181            z_render:growl_error("Sorry, you don't have permission to change the language list.", Context)
182    end;
183
184event({postback, {language_enable, Args}, _TriggerId, _TargetId}, Context) ->
185    case z_acl:is_allowed(use, ?MODULE, Context) of
186        true ->
187            {code, Code} = proplists:lookup(code, Args),
188            language_enable(Code, z_convert:to_bool(z_context:get_q("triggervalue", Context)), Context),
189            Context;
190        false ->
191            z_render:growl_error("Sorry, you don't have permission to change the language list.", Context)
192    end.
193
194
195%% @doc Set the language of the current user/session
196set_language(Code, [{CodeAtom, _Language}|Other], Context) ->
197    case z_convert:to_list(CodeAtom) of
198        Code ->   do_set_language(CodeAtom, Context);
199        _Other -> set_language(Code, Other, Context)
200    end.
201
202do_set_language(Code, Context) when is_atom(Code) ->
203    Context1 = z_context:set_language(Code, Context),
204    z_context:set_session(language, Code, Context1),
205    z_notifier:notify({language, Code}, Context1),
206    Context1.
207
208
209%% @doc Add a language to the i18n configuration
210language_add(OldIsoCode, NewIsoCode, Language, IsEnabled, Context) ->
211    IsoCodeNewAtom = z_convert:to_atom(z_string:to_name(z_string:trim(NewIsoCode))),
212    Languages = get_language_config(Context),
213    Languages1 = proplists:delete(OldIsoCode, Languages),
214    Languages2 = lists:usort([{IsoCodeNewAtom, 
215                                [{language, z_convert:to_binary(z_string:trim(z_html:escape(Language)))},
216                                 {is_enabled, z_convert:to_bool(IsEnabled)}
217                                ]} | Languages1]),
218    set_language_config(Languages2, Context).
219
220
221%% @doc Remove a language from the i18n configuration
222language_delete(IsoCode, Context) ->
223    Languages = get_language_config(Context),
224    Languages1 = proplists:delete(IsoCode, Languages),
225    set_language_config(Languages1, Context).
226
227%% @doc Set/reset the is_enabled flag of a language.
228language_enable(Code, IsEnabled, Context) ->
229    Languages = get_language_config(Context),
230    Lang = proplists:get_value(Code, Languages),
231    Lang1 = [{is_enabled, IsEnabled} | proplists:delete(is_enabled, Lang)],
232    Languages1 = lists:usort([{Code, Lang1} | proplists:delete(Code, Languages)]),
233    set_language_config(Languages1, Context).
234
235
236%% @doc Get the list of languages
237get_language_config(Context) ->
238    case m_config:get(i18n, language_list, Context) of
239        undefined -> [];
240        LanguageConfig -> proplists:get_value(list, LanguageConfig, [])
241    end.
242
243
244set_language_config(NewConfig, Context) ->
245    m_config:set_prop(i18n, language_list, list, NewConfig, Context).
246
247
248% @doc Generate all .po templates for the given site
249generate(#context{} = Context) ->
250    translation_po:generate(translation_scan:scan(Context));
251generate(Host) when is_atom(Host) ->
252    translation_po:generate(translation_scan:scan(z_context:new(Host))).
253
254
255
256%% do_choose/2 is adapted from webmachine_util:do_choose/3
257%% Original code copyright 2007-2008 Basho Technologies
258do_choose(Choices, Header) ->
259    Accepted = build_conneg_list(string:tokens(Header, ",")),
260    StarPrio = [P || {P,C} <- Accepted, C =:= "*"],
261    AnyOkay = case StarPrio of
262        [] -> no;
263        [0.0] -> no;
264        _ -> yes
265    end,
266    do_choose(AnyOkay, Choices, Accepted).
267do_choose(_AnyOkay, [], []) ->
268    none;
269do_choose(_AnyOkay, [], _Accepted) ->
270    none;
271do_choose(yes, Choices, []) ->
272    hd(Choices);
273do_choose(no, _Choices, []) ->
274    none;
275do_choose(AnyOkay, Choices, [AccPair|AccRest]) ->
276    {Prio, Acc} = AccPair,
277    case Prio of
278        0.0 ->
279            do_choose(AnyOkay, lists:delete(Acc, Choices), AccRest);
280        _ ->
281            LAcc = string:to_lower(Acc),
282            LChoices = [string:to_lower(X) || X <- Choices],
283            % doing this a little more work than needed in
284            % order to be easily insensitive but preserving
285            case lists:member(LAcc, LChoices) of
286                true -> 
287                    hd([X || X <- Choices,
288                             string:to_lower(X) =:= LAcc]);
289                false -> do_choose(AnyOkay, Choices, AccRest)
290            end
291    end.
292
293build_conneg_list(AccList) ->
294    build_conneg_list(AccList, []).
295build_conneg_list([], Result) -> lists:reverse(lists:sort(Result));
296build_conneg_list([Acc|AccRest], Result) ->
297    XPair = list_to_tuple([string:strip(X) || X <- string:tokens(Acc, ";")]),
298    Pair = case XPair of
299        {Choice, "q=" ++ PrioStr} ->
300            % Simplify "en-us" to "en"
301            [Choice1|_] = string:tokens(Choice, "-"), 
302            case PrioStr of
303                "0" -> {0.0, Choice1};
304                "1" -> {1.0, Choice1};
305                [$.|_] -> {list_to_float([$0|PrioStr]), Choice1};
306                _ -> {list_to_float(PrioStr), Choice1}
307            end;
308        {Choice} ->
309            {1.0, hd(string:tokens(Choice, "-"))}
310    end,
311    build_conneg_list(AccRest,[Pair|Result]).
312