From: Torbjorn T. <et...@us...> - 2005-07-18 23:22:48
|
Update of /cvsroot/jungerl/jungerl/lib/gettext/src In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv9498/gettext/src Added Files: Makefile gettext.app.src gettext.erl gettext.hrl gettext_app.erl gettext_compile.erl gettext_lib.hrl gettext_server.erl gettext_sup.erl iso639.erl Log Message: Added the 'gettext' application, which is a tool to help you internationalize your application. --- NEW FILE: gettext.app.src --- {application,gettext, [{description,"gettext handling"}, {vsn,"%VSN%"}, {modules,[%MODULES%]}, {registered,[]}, {env, []}, {mod, {gettext_app, []}}, {applications,[kernel,stdlib,sasl]}, {dependencies, [{kerbel, "2.9.6"}, {stdlib, "1.12.7"}, {sasl, "1.10.1"}]}]}. --- NEW FILE: gettext.hrl --- -ifndef(_GETTEXT_HRL). -define(_GETTEXT_HRL, true). -define(GETTEXT_HEADER_INFO, header_info). -define(DEFAULT_LANG, "en"). -define(DEFAULT_CHARSET, "iso-8859-1"). -define(EPOT_TABLE, epot). -endif. --- NEW FILE: gettext_app.erl --- %%%---------------------------------------------------------------------- %%% File : gettext_app.erl %%% Author : to...@bl... %%% Purpose : gettext handling %%% Created : 28 Oct 2003 %%% %%% $Id: gettext_app.erl,v 1.1 2005/07/18 23:22:35 etnt Exp $ %%%---------------------------------------------------------------------- -module(gettext_app). -behaviour(application). %% application callbacks -export([start/2, stop/1]). %%%---------------------------------------------------------------------- %%% Callback functions from application %%%---------------------------------------------------------------------- %%---------------------------------------------------------------------- %% Func: start/2 %% Returns: {ok, Pid} | %% {ok, Pid, State} | %% {error, Reason} %%---------------------------------------------------------------------- start(_Type, _StartArgs) -> case gettext_sup:start_link() of {ok, Pid} -> {ok, Pid}; Error -> Error end. %%---------------------------------------------------------------------- %% Func: stop/1 %% Returns: any %%---------------------------------------------------------------------- stop(_State) -> ok. %%%---------------------------------------------------------------------- %%% Internal functions %%%---------------------------------------------------------------------- --- NEW FILE: gettext_sup.erl --- %%%---------------------------------------------------------------------- %%% File : gettext_sup.erl %%% Author : to...@bl... %%% Purpose : Supervisor for the gettext handling %%% Created : 28 Oct 2003 %%% %%% $Id: gettext_sup.erl,v 1.1 2005/07/18 23:22:35 etnt Exp $ %%%---------------------------------------------------------------------- -module(gettext_sup). -behaviour(supervisor). %% External exports -export([start_link/0]). %% supervisor callbacks -export([init/1]). %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- start_link() -> supervisor:start_link({local, gettext_sup}, gettext_sup, []). %%%---------------------------------------------------------------------- %%% Callback functions from supervisor %%%---------------------------------------------------------------------- %%---------------------------------------------------------------------- %% Func: init/1 %% Returns: {ok, {SupFlags, [ChildSpec]}} | %% ignore | %% {error, Reason} %%---------------------------------------------------------------------- init([]) -> CallBackMod = gettext_server, % YOU SHOULD POSSIBLY CHANGE THIS !! GettextServer = {gettext_server,{gettext_server,start_link,[CallBackMod]}, permanent,5000,worker,[gettext_server]}, {ok,{{one_for_one,3,10}, [GettextServer]}}. %%%---------------------------------------------------------------------- %%% Internal functions %%%---------------------------------------------------------------------- --- NEW FILE: gettext_lib.hrl --- -ifndef(_GETTEXT_LIB_HRL). -define(_GETTEXT_LIB_HRL, true). -record(epot, { msgid, % The String file_info % List of '{Filename, LineNo}' tuples }). -endif. --- NEW FILE: gettext_compile.erl --- -module(gettext_compile). %%%---------------------------------------------------------------------- %%% Created: 27 Oct 2003 by to...@bl... %%% Function: Tools for multi-lingual capabilities, %%% similar to GNU gettext. %%% %%% NB: MAKE SURE TO NOT CALL ANY OTHER MODULE IN THE SYSTEM. %%% THIS CODE RUNS IN A PRE-BUILD PHASE !!! %%% %%% $Id: gettext_compile.erl,v 1.1 2005/07/18 23:22:35 etnt Exp $ %%%---------------------------------------------------------------------- -export([parse_transform/2, epot2po/0]). -include("gettext.hrl"). -ifdef(debug). %%-define(debug(S,A), io:format( S, A)). -define(debug(S,A), io:format(get(fd), S, A)). -else. %%-define(debug(S,A), io:format(get(fd), S, A)). -define(debug(S,A), true). -endif. %%% -------------------------------------------------------------------- %%% From the Erlang po-Template file, create a GNU po-file. %%% -------------------------------------------------------------------- epot2po() -> {Gettext_App_Name, GtxtDir, DefLang} = get_env(), open_epot_file(Gettext_App_Name, GtxtDir), Es = lists:keysort(1, get_epot_data()), close_epot_file(), open_po_file(Gettext_App_Name, GtxtDir, DefLang), write_header(), write_entries(Es), close_file(), init:stop(). write_entries(L) -> Fd = get(fd), F = fun({Id,Finfo}) -> Fi = fmt_fileinfo(Finfo), io:format(Fd, "~n#: ~s~n", [Fi]), file:write(Fd, "msgid \"\"\n"), write_pretty(Id), file:write(Fd, "msgstr \"\"\n"), write_pretty(Id) end, lists:foreach(F, L). -define(ENDCOL, 72). -define(PIVOT, 4). -define(SEP, $\s). write_pretty(Str) -> write_pretty(Str, get(fd)). write_pretty([], _) -> true; write_pretty(Str, Fd) when length(Str) =< ?ENDCOL -> write_string(Str, Fd); write_pretty(Str, Fd) -> {Line, Rest} = get_line(Str), write_string(Line, Fd), write_pretty(Rest, Fd). write_string(Str, Fd) -> file:write(Fd, "\""), file:write(Fd, Str), file:write(Fd, "\"\n"). %%% Split the string into substrings, %%% aligned around a specific column. get_line(Str) -> get_line(Str, ?SEP, 1, ?ENDCOL, []). %%% End of string reached. get_line([], _Sep, _N, _End, Acc) -> {lists:reverse(Acc), []}; %%% Eat characters. get_line([H|T], Sep, N, End, Acc) when N < End -> get_line(T, Sep, N+1, End, [H|Acc]); %%% Ended with a Separator on the End boundary. get_line([Sep|T], Sep, End, End, Acc) -> {lists:reverse([Sep|Acc]), T}; %%% At the end, try to find end of token within %%% the given constraint, else backup one token. get_line([H|T] = In, Sep, End, End, Acc) -> case find_end(T, Sep) of {true, Racc, Rest} -> {lists:reverse(Racc ++ [H|Acc]), Rest}; false -> case reverse_tape(Acc, In) of {true, Bacc, Rest} -> {lists:reverse(Bacc), Rest}; {false,Str} -> %%% Ugh...the word is longer than ENDCOL... split_string(Str, ?ENDCOL) end end. find_end(Str, Sep) -> find_end(Str, Sep, 1, ?PIVOT, []). find_end([Sep|T], Sep, N, Pivot, Acc) when N =< Pivot -> {true, [Sep|Acc], T}; find_end(_Str, _Sep, N, Pivot, _Acc) when N > Pivot -> false; find_end([H|T], Sep, N, Pivot, Acc) -> find_end(T, Sep, N+1, Pivot, [H|Acc]); find_end([], _Sep, _N, _Pivot, Acc) -> {true, Acc, []}. reverse_tape(Acc, Str) -> reverse_tape(Acc, Str, ?SEP). reverse_tape([Sep|_T] = In, Str, Sep) -> {true, In, Str}; reverse_tape([H|T], Str, Sep) -> reverse_tape(T, [H|Str], Sep); reverse_tape([], Str, _Sep) -> {false, Str}. split_string(Str, End) -> split_string(Str, End, 1, []). split_string(Str, End, End, Acc) -> {lists:reverse(Acc), Str}; split_string([H|T], End, N, Acc) when N < End -> split_string(T, End, N+1, [H|Acc]); split_string([], _End, _N, Acc) -> {lists:reverse(Acc), []}. fmt_fileinfo(Finfo) -> F = fun({Fname,LineNo}, Acc) -> Fname ++ ":" ++ to_list(LineNo) ++ [$\s|Acc] end, lists:foldr(F,[],Finfo). write_header() -> io:format(get(fd), "# SOME DESCRIPTIVE TITLE.\n" "# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER\n" "# This file is distributed under the same license as the PACKAGE package.\n" "# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\n" "#\n" "# NB: Consider using poEdit <http://poedit.sourceforge.net>\n" "#\n" "#\n" "#, fuzzy\n" "msgid \"\"\n" "msgstr \"\"\n" "\"Project-Id-Version: PACKAGE VERSION\\n\"\n" "\"POT-Creation-Date: 2003-10-21 16:45+0200\\n\"\n" "\"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n\"\n" "\"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n\"\n" "\"Language-Team: LANGUAGE <LL...@li...>\\n\"\n" "\"MIME-Version: 1.0\\n\"\n" "\"Content-Type: text/plain; charset=iso-8859-1\\n\"\n" "\"Content-Transfer-Encoding: 8bit\\n\"\n", []). %%% -------------------------------------------------------------------- %%% NB: We assume that the surrounding code does some preparations: %%% %%% 1. Setup the environment variables: 'gettext_dir' and 'gettext_tmp_name' %%% %%% 2. The compiler is called with the 'gettext' flag. %%% %%% 3. The file $(gettext_dir)/lang/$(gettext_tmp_name)/epot.dets is %%% removed before the first erlang/yaws file is processed. %%% (entrys are appended to the file) %%% -------------------------------------------------------------------- parse_transform(Form,Opts) -> case lists:member(gettext, Opts) of true -> {Gettext_App_Name, GtxtDir, _} = get_env(), open_epot_file(Gettext_App_Name, GtxtDir), ?debug( "--- Opts --- ~p~n",[Opts]), ?debug("--- Env --- isd_type=~p , gettext_dir=~p~n", [Gettext_App_Name,GtxtDir]), pt(Form, Opts), close_file(), Form; _ -> Form end. get_env() -> {os:getenv("gettext_tmp_name"), os:getenv("gettext_dir"), os:getenv("gettext_def_lang")}. pt(Form, Opts) -> put(fname, ""), pt(Form, Opts, undefined). pt([H|T],Opts,Func) when list(H) -> ?debug( "--- 1 --- ~p~n",[H]), F = fun (X) -> pt(X,Opts,Func) end, [lists:map(F,H)|pt(T,Opts,Func)]; %%% pt({call,L1,{remote,L2,{atom,L3,gettext},{atom,L4,key2str}}, [{string,L5,String}]}, _Opts, _Func) -> ?debug( "++++++ String=<~p>~n",[String]), dump(String, L5), {call,L1, {remote,L2, {atom,L3,gettext}, {atom,L4,key2str}}, [{string,L5,String}]}; %%% pt([{call,_,{remote,_,{atom,_,gettext},{atom,_,key2str}}, [{string,L5,String}]} = H | T], Opts, Func) -> ?debug( "++++++ String=<~p>~n",[String]), dump(String, L5), [H | pt(T, Opts, Func)]; %%% pt([{attribute,_L,module,Mod} = H | T], Opts, Func) -> put(fname, to_list(Mod) ++ ".erl"), ?debug( "++++++ Filename 1 =<~p>~n",[get(fname)]), [H | pt(T, Opts, Func)]; %%% pt([{attribute,_L,yawsfile,Fname} = H | T], Opts, Func) -> put(fname, to_list(Fname)), ?debug( "++++++ Filename 2 =<~p>~n",[get(fname)]), [H | pt(T, Opts, Func)]; %%% pt([{block,N,B}|T], Opts, Func) -> ?debug( "--- 2 --- ~p~n",[block]), Block = {block,N,pt(B,Opts,Func)}, [Block|pt(T, Opts, Func)]; %%% pt([H|T], Opts, Func) when tuple(H) -> ?debug( "--- 3 --- ~p~n",[H]), [while(size(H), H, Opts, Func) | pt(T, Opts, Func)]; %%% pt([H|T], Opts, Func) -> ?debug( "--- 4 --- ~p~n",[H]), [H | pt(T, Opts, Func)]; %%% pt(T, Opts, Func) when tuple(T) -> ?debug( "--- 5 --- ~p~n",[T]), while(size(T), T, Opts, Func); %%% pt(X, _, _) -> ?debug( "--- 6 --- ~p~n",[X]), X. while(_,{block,N,B},Opts,Func) -> {block,N,pt(B,Opts,Func)}; while(N,T,Opts,Func) when N>0 -> NT = setelement(N,T,pt(element(N,T),Opts,Func)), while(N-1,NT,Opts,Func); while(0,T,_,_) -> T. dump(Str,L) -> Fname = get(fname), Finfo = get_file_info(Str), dets:insert(?EPOT_TABLE, {escape_chars(Str), [{Fname,L}|Finfo]}). get_file_info(Key) -> case dets:lookup(?EPOT_TABLE, Key) of [] -> []; [{_,Finfo}|_] -> Finfo end. escape_chars(Str) -> F = fun($", Acc) -> [$\\,$"|Acc]; ($\\, Acc) -> [$\\,$\\|Acc]; ($\n, Acc) -> [$\\,$n|Acc]; (C, Acc) -> [C|Acc] end, lists:foldr(F, [], Str). open_epot_file(Gettext_App_Name, GtxtDir) -> Fname = mk_epot_fname(Gettext_App_Name, GtxtDir), filelib:ensure_dir(Fname), {ok, _} = dets:open_file(?EPOT_TABLE, [{file, Fname}]). close_epot_file() -> dets:close(?EPOT_TABLE). get_epot_data() -> dets:foldl(fun(E, Acc) -> [E|Acc] end, [], ?EPOT_TABLE). mk_epot_fname(Gettext_App_Name, GtxtDir) -> GtxtDir ++ "/lang/" ++ Gettext_App_Name ++ "/epot.dets". open_po_file(Gettext_App_Name, GtxtDir, DefLang) -> DefDir = filename:join([GtxtDir, "lang", Gettext_App_Name, DefLang]), Fname = filename:join([DefDir, "gettext.po"]), filelib:ensure_dir(Fname), {ok,Fd} = file:open(Fname, [write]), put(fd,Fd). close_file() -> file:close(get(fd)). to_list(A) when atom(A) -> atom_to_list(A); to_list(I) when integer(I) -> integer_to_list(I); to_list(B) when binary(B) -> binary_to_list(B); to_list(L) when list(L) -> L. --- NEW FILE: gettext.erl --- -module(gettext). -compile(export_all). %%%---------------------------------------------------------------------- %%% Created: 27 Oct 2003 by to...@bl... %%% Function: Tools for multi-lingual capabilities, %%% similar to GNU gettext. %%% %%% $Id: gettext.erl,v 1.1 2005/07/18 23:22:35 etnt Exp $ %%%---------------------------------------------------------------------- -export([key2str/1, parse_po/1, lc2lang/1, parse_po_bin/1, all_lang/0, key2str/2]). -include("gettext.hrl"). %%% -------------------------------------------------------------------- %%% This is the lookup routines. %%% -------------------------------------------------------------------- %%% Hopefully, the surrounding code has done its job and %%% put the language to be used in the process dictionary. key2str(Key) -> gettext_server:key2str(Key, get(gettext_language)). key2str(Key, Lang) -> gettext_server:key2str(Key, Lang). %%% -------------------------------------------------------------------- %%% In case the string is used in a javascript context, %%% we need to take care of quotes. %%% -------------------------------------------------------------------- quotes([$'|T]) -> [$\\,$' | quotes(T)]; quotes([$"|T]) -> [$\\,$" | quotes(T)]; quotes([H|T]) -> [H | quotes(T)]; quotes([]) -> []. %%% -------------------------------------------------------------------- %%% Parse a PO-file %%% -------------------------------------------------------------------- parse_po(Fname) -> {ok,Bin} = file:read_file(Fname), parse_po_bin(Bin). parse_po_bin(Bin) -> parse_po_file(to_list(Bin)). parse_po_file("msgid" ++ T) -> {Key, R0} = get_po_string(T), {Val, Rest} = get_msgstr(R0), [{Key,Val} | parse_po_file(Rest)]; parse_po_file([_ | T]) -> parse_po_file(T); parse_po_file([]) -> []. get_msgstr("msgstr" ++ T) -> get_po_string(T); get_msgstr([_ | T]) -> get_msgstr(T). %%% %%% A PO-string has the same syntax as a C character string. %%% For example: %%% %%% msgstr "" %%% "Hello " %%% %%% "\\World\n" %%% %%% Is parsed as: "Hello \World\n" %%% get_po_string([$\s|T]) -> get_po_string(T); get_po_string([$\r|T]) -> get_po_string(T); get_po_string([$\n|T]) -> get_po_string(T); get_po_string([$\t|T]) -> get_po_string(T); get_po_string([$"|T]) -> header_info(eat_string(T)). %%% only header-info has empty po-string ! header_info({"",R}) -> {?GETTEXT_HEADER_INFO, R}; header_info(X) -> X. eat_string(S) -> eat_string(S,[]). eat_string([$\\,$"|T], Acc) -> eat_string(T, [$"|Acc]); % unescape ! eat_string([$\\,$\\ |T], Acc) -> eat_string(T, [$\\|Acc]); % unescape ! eat_string([$\\,$n |T], Acc) -> eat_string(T, [$\n|Acc]); % unescape ! eat_string([$"|T], Acc) -> eat_more(T,Acc); eat_string([H|T], Acc) -> eat_string(T, [H|Acc]). eat_more([$\s|T], Acc) -> eat_more(T, Acc); eat_more([$\n|T], Acc) -> eat_more(T, Acc); eat_more([$\r|T], Acc) -> eat_more(T, Acc); eat_more([$\t|T], Acc) -> eat_more(T, Acc); eat_more([$"|T], Acc) -> eat_string(T, Acc); eat_more(T, Acc) -> {lists:reverse(Acc), T}. to_list(A) when atom(A) -> atom_to_list(A); to_list(I) when integer(I) -> integer_to_list(I); to_list(B) when binary(B) -> binary_to_list(B); to_list(L) when list(L) -> L. %%% -------------------------------------------------------------------- %%% Language Codes %%% -------------------------------------------------------------------- lc2lang(LC) -> case iso639:lc3lang(LC) of "" -> iso639:lc2lang(LC); % backward compatible Lang -> Lang end. all_lang() -> iso639:all3lang(). --- NEW FILE: Makefile --- include ../../../support/include.mk include ../vsn.mk VSN=$(GETTEXT_VSN) MODULES=gettext \ gettext_compile \ gettext_server \ gettext_sup \ gettext_app \ iso639 EBIN_FILES=$(MODULES:%=../ebin/%.$(EMULATOR)) ../ebin/gettext.app ERLC_FLAGS+=-W INCLUDES= # # Targets # all: $(EBIN_FILES) gettext_compile: ../ebin/gettext_compile.$(EMULATOR) debug: $(MAKE) ERLC_FLAGS+="$(ERLC_FLAGS) +debug_info -Ddebug" imsg: $(MAKE) ERLC_FLAGS+="-Dimsg" clean: rm -f $(EBIN_FILES) ../ebin/*.$(EMULATOR): $(INCLUDES) --- NEW FILE: iso639.erl --- %%%------------------------------------------------------------------- %%% Created : 9 Mar 2004 by <to...@bl...> %%% Description : ISO 639 2- and 3-letter codes. %%%------------------------------------------------------------------- -module(iso639). -export([lc2lang/1, all2lang/0, lc3lang/1, all3lang/0]). %%%--------------------------------------------------------------------- %%% We should only be using 3-letter codes. %%% However, a couple of languages seems to only have a 2-letter code. %%%--------------------------------------------------------------------- lc3lang("abk") -> "Abkhazian"; lc3lang("ace") -> "Achinese"; lc3lang("ach") -> "Acoli"; lc3lang("ada") -> "Adangme"; lc3lang("aar") -> "Afar"; lc3lang("afh") -> "Afrihili"; lc3lang("afr") -> "Afrikaans"; [...1128 lines suppressed...] {"tr", "Turkish"}, {"ts", "Tsonga"}, {"tt", "Tatar"}, {"tw", "Twi"}, {"ty", "Tahitian"}, {"ug", "Uighur"}, {"uk", "Ukrainian"}, {"ur", "Urdu"}, {"uz", "Uzbek"}, {"vi", "Vietnamese"}, {"vo", "Volapuk"}, {"wa", "Walloon"}, {"wo", "Wolof"}, {"xh", "Xhosa"}, {"yi", "Yiddish (formerly ji)"}, {"yo", "Yoruba"}, {"za", "Zhuang"}, {"zh", "Chinese"}, {"zu", "Zulu"}]. --- NEW FILE: gettext_server.erl --- %%%------------------------------------------------------------------- %%% File : gettext_server.erl %%% Author : Torbjorn Tornkvist <to...@bl...> %%% Desc. : Internationalization support. %%% Created : 28 Oct 2003 by Torbjorn Tornkvist <to...@bl...> %%% %%% $Id: gettext_server.erl,v 1.1 2005/07/18 23:22:35 etnt Exp $ %%%------------------------------------------------------------------- -module(gettext_server). -behaviour(gen_server). %%-------------------------------------------------------------------- %% Include files %%-------------------------------------------------------------------- %%-------------------------------------------------------------------- %% External exports -export([start/0, start_link/0, start/1, start_link/1, key2str/2, store_pofile/2, all_lang/0, lang2cset/1]). %% Default callback functions -export([custom_dir/0]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -include("gettext.hrl"). -define(elog(X,Y), error_logger:info_msg("*elog ~p:~p: " X, [?MODULE, ?LINE | Y])). -define(SERVER, ?MODULE). -define(TABLE_NAME, gettext_db). -define(KEY(Lang,Key), {Key,Lang}). -define(ENTRY(Lang, Key, Val), {?KEY(Lang,Key), Val}). -record(state, { cbmod = ?MODULE, % callback module cache = [], % list_of( #cache{} ) gettext_dir % Dir where all the data are stored }). %%% %%% Hold info about the languages stored. %%% -record(cache, { language = ?DEFAULT_LANG, charset = ?DEFAULT_CHARSET }). %%==================================================================== %% External functions %%==================================================================== %%-------------------------------------------------------------------- %% Function: start_link/0 %% Description: Starts the server %%-------------------------------------------------------------------- start_link() -> start_link(?MODULE). start_link(CallBackMod) -> gen_server:start_link({local, ?SERVER}, ?MODULE, CallBackMod, []). start() -> start(?MODULE). start(CallBackMod) -> gen_server:start({local, ?SERVER}, ?MODULE, CallBackMod, []). key2str(Key, Lang) -> gen_server:call(?SERVER, {key2str, Key, Lang}, infinity). lang2cset(Lang) -> gen_server:call(?SERVER, {lang2cset, Lang}, infinity). store_pofile(Lang, File) when binary(File) -> gen_server:call(?SERVER, {store_pofile, Lang, File}, infinity). all_lang() -> L = dets:match(gettext_db, {{header_info, '$1'}, '_'}), [hd(X) || X <- L]. %%==================================================================== %% Server functions %%==================================================================== %%-------------------------------------------------------------------- %% Function: init/1 %% Description: Initiates the server %% Returns: {ok, State} | %% {ok, State, Timeout} | %% ignore | %% {stop, Reason} %%-------------------------------------------------------------------- init(CallBackMod0) -> CallBackMod = get_callback_mod(CallBackMod0), GettextDir = get_gettext_dir(CallBackMod), Fname = filename:join(GettextDir, "gettext_db.dets"), filelib:ensure_dir(Fname), Cache = init_db_table(GettextDir, Fname), {ok, #state{cache = Cache, cbmod = CallBackMod, gettext_dir = GettextDir}}. %%% %%% The GETTEXT_CBMOD environment variable takes precedence! %%% get_callback_mod(CallBackMod0) -> case os:getenv("GETTEXT_CBMOD") of false -> CallBackMod0; CbMod -> list_to_atom(CbMod) end. %%% %%% The GETTEXT_DIR environment variable takes precedence! %%% Next we will try to get hold of the value from the callback. %%% get_gettext_dir(CallBackMod) -> case os:getenv("GETTEXT_DIR") of false -> case catch CallBackMod:gettext_dir() of Dir when list(Dir) -> Dir; _ -> code:priv_dir(gettext) % fallback end; Dir -> Dir end. %% Default callback function custom_dir() -> code:priv_dir(gettext). %%-------------------------------------------------------------------- %% Function: handle_call/3 %% Description: Handling call messages %% Returns: {reply, Reply, State} | %% {reply, Reply, State, Timeout} | %% {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, Reply, State} | (terminate/2 is called) %% {stop, Reason, State} (terminate/2 is called) %%-------------------------------------------------------------------- handle_call({key2str, Key, Lang}, _From, State) -> Reply = lookup(Lang, Key), {reply, Reply, State}; %% handle_call({lang2cset, Lang}, _From, State) -> Reply = case lists:keysearch(Lang, #cache.language, State#state.cache) of false -> {error, "not found"}; {value, C} -> {ok, C#cache.charset} end, {reply, Reply, State}; %% handle_call({store_pofile, Lang, File}, _From, State) -> GettextDir = State#state.gettext_dir, NewCache = do_store_pofile(Lang, File, GettextDir, State#state.cache), {reply, ok, State#state{cache = NewCache}}. %%-------------------------------------------------------------------- %% Function: handle_cast/2 %% Description: Handling cast messages %% Returns: {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} (terminate/2 is called) %%-------------------------------------------------------------------- handle_cast(_Msg, State) -> {noreply, State}. %%-------------------------------------------------------------------- %% Function: handle_info/2 %% Description: Handling all non call/cast messages %% Returns: {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} (terminate/2 is called) %%-------------------------------------------------------------------- handle_info(_Info, State) -> {noreply, State}. %%-------------------------------------------------------------------- %% Function: terminate/2 %% Description: Shutdown the server %% Returns: any (ignored by gen_server) %%-------------------------------------------------------------------- terminate(_Reason, _State) -> ok. %%-------------------------------------------------------------------- %% Func: code_change/3 %% Purpose: Convert process state when code is changed %% Returns: {ok, NewState} %%-------------------------------------------------------------------- code_change(_OldVsn, State, _Extra) -> {ok, State}. %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- do_store_pofile(Lang, File, GettextDir, Cache) -> Dir = filename:join([GettextDir, "lang", "custom", Lang]), Fname = filename:join([Dir, "gettext.po"]), filelib:ensure_dir(Fname), case file:write_file(Fname, File) of ok -> case lists:keymember(Lang, #cache.language, Cache) of true -> delete_lc(Lang); false -> false end, insert_po_file(Lang, Fname), {ok, set_charset(#cache{language = Lang})}; _ -> {error, "failed to write PO file to disk"} end. set_charset(C) -> case lookup(C#cache.language, ?GETTEXT_HEADER_INFO) of ?GETTEXT_HEADER_INFO -> % nothing found... C#cache{charset = ?DEFAULT_CHARSET}; % fallback Pfinfo -> CharSet = get_charset(Pfinfo), C#cache{charset = CharSet} end. get_charset(Pfinfo) -> g_charset(string:tokens(Pfinfo,[$\n])). g_charset(["Content-Type:" ++ Rest|_]) -> g_cset(Rest); g_charset([_H|T]) -> g_charset(T); g_charset([]) -> ?DEFAULT_CHARSET. g_cset("charset=" ++ Charset) -> rm_trailing_stuff(Charset); g_cset([_|T]) -> g_cset(T); g_cset([]) -> ?DEFAULT_CHARSET. rm_trailing_stuff(Charset) -> lists:reverse(eat_dust(lists:reverse(Charset))). eat_dust([$\s|T]) -> eat_dust(T); eat_dust([$\n|T]) -> eat_dust(T); eat_dust([$\r|T]) -> eat_dust(T); eat_dust([$\t|T]) -> eat_dust(T); eat_dust(T) -> T. init_db_table(GettextDir, TableFile) -> case filelib:is_regular(TableFile) of false -> create_and_populate(GettextDir, TableFile); true -> %% If the dets file is broken, dets may not be able to repair it %% itself (it may be only half-written). So check and recreate %% if needed instead. case open_dets_file(?TABLE_NAME, TableFile) of ok -> create_cache(); _ -> create_and_populate(GettextDir, TableFile) end end. create_cache() -> F = fun(LC, Acc) -> case lookup(LC, ?GETTEXT_HEADER_INFO) of ?GETTEXT_HEADER_INFO -> %% nothing found... ?elog("Could not find header info for lang: ~s~n",[LC]), Acc; Pfinfo -> CS = get_charset(Pfinfo), [#cache{language = LC, charset = CS}|Acc] end end, lists:foldl(F, [], all_lang()). create_and_populate(GettextDir, TableFile) -> %% Need to create and populate the DB. {ok, _} = dets:open_file(?TABLE_NAME, [{file, TableFile}, %% creating on disk, esp w auto_save, %% takes "forever" on flash disk {ram_file, true}]), L = populate_db(GettextDir), dets:close(?TABLE_NAME), % flush to disk {ok, _} = dets:open_file(?TABLE_NAME, [{file, TableFile}]), L. open_dets_file(Tname, Fname) -> Opts = [{file, Fname}, {repair, false}], case dets:open_file(Tname, Opts) of {ok, _} -> ok; _ -> file:delete(Fname), error end. %%% %%% Insert the given languages into the DB. %%% %%% NB: It is important to insert the 'predefiend' language %%% definitions first since a custom language should be %%% able to 'shadow' the the same predefined language. %%% populate_db(GettextDir) -> L = insert_predefined(GettextDir, []), insert_custom(GettextDir, L). insert_predefined(GettextDir, L) -> Dir = filename:join([GettextDir, "lang", "default"]), insert_data(Dir, L). insert_data(Dir, L) -> case file:list_dir(Dir) of {ok, Dirs} -> F = fun([$.|_], Acc) -> Acc; % ignore in a local inst. env. ("CVS" ++ _, Acc) -> Acc; % ignore in a local inst. env. (LC, Acc) -> Fname = filename:join([Dir, LC, "gettext.po"]), insert_po_file(LC, Fname), [#cache{language = LC} | Acc] end, lists:foldl(F, L, Dirs); _ -> L end. insert_po_file(LC, Fname) -> case file:read_file_info(Fname) of {ok, _} -> insert(LC, gettext:parse_po(Fname)); _ -> ?elog("gettext_server: Could not read ~s~n", [Fname]), {error, "could not read PO file"} end. insert_custom(GettextDir, L) -> Dir = filename:join([GettextDir, "lang", "custom"]), insert_data(Dir, L). insert(LC, L) -> F = fun({Key, Val}) -> dets:insert(?TABLE_NAME, ?ENTRY(LC, Key, Val)) end, lists:foreach(F, L). lookup(Lang, Key) -> case dets:lookup(?TABLE_NAME, ?KEY(Lang, Key)) of [] -> Key; [{_,Str}|_] -> Str end. delete_lc(LC) -> dets:match_delete(?TABLE_NAME, ?ENTRY(LC, '_', '_')). |