Thread: RE: [htmltmpl] RFC: Persistent or 2-stage evaluation
Brought to you by:
samtregar
From: Ragan, S. <sr...@bc...> - 2005-01-18 20:57:56
|
Approach the problem from a slightly different tack. Don't try to do everything in one pass through H::T. Instead, think of it as a two-step approach. Each user belongs to a group, and the group portions of the template are the same for all members of that group. Only the user portions need to change with each visit. So, separate the group variables from the user variables. In this type situation, I do a one-time "page generation" of the "group" template to be used for subsequent user visits. That user template is generated from a "master template" where all the "group" level elements (like logo image, background and text colors, etc.) are normal TMPL_VAR's to be handled during this page gen step. Portions that pertain to user-specific variables are "renamed" (or purposely misnamed, if you will) TMPL_VAR's so they are not recognized and handled by H::T during this pass. So, the user's first name, which should be "ignored" during the page gen might be in a "tag" named like - <my_special_VAR NAME=first> In my page gen code I take the output H::T and rename the dummy VAR names to "proper" H::T TMPL_VAR's before writing out the user version of the template. Assuming you've got an H::T object, $template, and previously opened FileHandle, $fh, the code would look something like this - my $output = $template->output; ## Replace temporary tags with HTML::Temlate tags before write to file # $output =~ s/my_special_VAR/TMPL_VAR/g; print $fh $output; The template output in the output file pointed to by $fh is then your "user" version of the template. All the "group-level" elements have already been replaced with the hard-coded equivalents. Makes for a much faster end-user experience since there are less tags to process at that level. Steve Ragan Sr. Internet Developer Harris Internet Services 2500 Westchester Ave. Purchase, NY 10577 Phone: 914-641-3948 Fax : 914-641-3901 sr...@bc... -----Original Message----- From: htm...@li... [mailto:htm...@li...]On Behalf Of Mark Fuller Sent: Tuesday, January 18, 2005 12:26 PM To: HTML::Template users Subject: [htmltmpl] RFC: Persistent or 2-stage evaluation Sam, earlier I said it would be useful if I could apply some evaluation once. Then operate upon that partially evaluated template. I didn't realize H::T applies everything at output(?). If H::T applies everything at output, wouldn't it be relatively easy to accomplish this if there was a way to tell H::T to perform an "output" *but don't eliminate any H::T tags that are unevaluated*? If I could do this, I could set all my one-time page evaluation and do a "new_scalar_ref->" (using the output from my first "new->" from a file). If there were a way to tell H::T to reload it internally there may be a way to utilize H::T's caching mechanism (instead of me keeping my H::T object in %hash_of_templates{language}). Doing a "new->" would have to tell me it reloaded the template so that I could do the one-time processing again (and output_internal-> to put it back into the state where it's mostely prepared for repeated processing). What do you think? Would it be easy to do by sub-classing? Did you eventually agree there is a legitimate case for one-time page evaluation? (One-time language replacement of title, headings, navigation, etc.) I'd like to apply some heavy replacement and do it only once. Keep the resulting text containing only the tags that are evaluated on a display-by-display basis (messages, etc.). This might have some application for select/option lists too. I won't confuse the issue with that yet. My main concern is just to avoid repetitive numerous replacement of text which is constant for a page. Thanks! Mark ------------------------------------------------------- The SF.Net email is sponsored by: Beat the post-holiday blues Get a FREE limited edition SourceForge.net t-shirt from ThinkGeek. It's fun and FREE -- well, almost....http://www.thinkgeek.com/sfshirt _______________________________________________ Html-template-users mailing list Htm...@li... https://lists.sourceforge.net/lists/listinfo/html-template-users |
From: Mark F. <mar...@ea...> - 2005-01-18 23:47:37
|
From: "Ragan, Steve" <sr...@bc...> > Portions that > pertain to user-specific variables are "renamed" (or purposely misnamed, if > you will) TMPL_VAR's so they are not recognized and handled by H::T during > this pass. > > <my_special_VAR NAME=first> > > [then] > > $output =~ s/my_special_VAR/TMPL_VAR/g; Steve, thanks. That might work for me. I'm not sure I'd want to pre-generate all the pages (and keep them in synch). Since I'm caching my own pages (by language), I could probably do the ->new, ->output, regex and ->new_scaler_ref when a page is not in the cache without too much slowdown on the first display of a page. The three things I've found that make multi-language processing difficult with H::T are: 1. One-time evaluation of a template's page-specific vars (so that subsequent displays can deal only in truly variable evaluations). 2. No way to say <tmpl_include name="constant_text_<tmpl_var name=LANG>"> to use language-specific text determined at run time. 3. No way to recurse variables which may have been evaluated with text that contains variables. #2 and 3 are troubling because, if I can't pull in fragments of a page (for a language), then I'm likely to use "Locale::Maketext" to provide me with what may be lengthy fragments. If the fragments contain markup (like a link), then I'm stuck because the fragment's link will contain an attribute: "title=<TMPL_VAR NAME=LINK_TITLE_TEXT>" which will not be evaluated. Feeding ->output back into new H::T objects can get around a lot of this. It would be a lot easier if those 3 features were available. They don't seem like they would require too much kludging. Thanks, Mark |
From: Mathew R. <mat...@re...> - 2005-01-19 03:08:12
|
> The three things I've found that make multi-language processing = difficult > with H::T are: >=20 > 1. One-time evaluation of a template's page-specific vars (so that > subsequent displays can deal only in truly variable evaluations). > 2. No way to say <tmpl_include name=3D"constant_text_<tmpl_var = name=3DLANG>"> to > use language-specific text determined at run time. > 3. No way to recurse variables which may have been evaluated with text = that > contains variables. >=20 > #2 and 3 are troubling because, if I can't pull in fragments of a page = (for > a language), then I'm likely to use "Locale::Maketext" to provide me = with > what may be lengthy fragments. If the fragments contain markup (like a > link), then I'm stuck because the fragment's link will contain an = attribute: > "title=3D<TMPL_VAR NAME=3DLINK_TITLE_TEXT>" which will not be = evaluated. >=20 > Feeding ->output back into new H::T objects can get around a lot of = this. It > would be a lot easier if those 3 features were available. They don't = seem > like they would require too much kludging. FWIW, the company I work for uses H::T for page generation, and we = handle an arbitrary number of languages. Currently we have the product = translated into 4 different languages, and we have two more on the way. = H::T is used in combination with the Locale::MakePhrase package.... = however, I use a modified H::T.... The reason that I made a custom package of H::T was that the only = solutions that I could see to make language translations work was: a) provide a different template for each language, then use the = appropriate one based on users' language b) use H::T::E with a callback function so that static template strings = could run though the translation engine b) modify H::T so that it supports custom TMPL_xxx tags so that static = template strings could run through the translation engine Points (a) and (b) require no changes to H::T, and I can say from = experience that it works reasonably well. However, I found: <TMPL_VAR EXPR=3D"catalog('some static string')">=20 to be more nasty than: <TMPL_CATALOG "some static string"> so I chose to modify H::T. In the process, I added some extra features = to H::T. If you need more info on how I do language translations, let me know... Mathew |
From: Mark F. <mar...@ea...> - 2005-01-19 05:37:22
|
From: "Mathew Robertson" <mat...@re...> > >provide a different template for each language I initially went this way. But, I didn't like duplicating page structure. I found that I *really* like to keep everything in Locale::Maketext lexicons. But, the downsides are: 1. The Locale::Maketext is geared towards one large project. Not small pages. The more I moved constant/static text out of templates, the more the lexicon grew with text individual C::A modules would never need. That's a lot to load. 2. I have a lot of TMPL_VARs to replace with Locale::Maketext calls. Which leads me to want to perform an initialization of the template with replacement of text that would have more naturally been in the template if it were not for language differences. 3. As those one-time TMPL_VARs grow in number, they worry me that they add to the re-evaluation of the template. Even *if* I go through an initialization process to perform all the PARAMs only once, they'll still be re-evaluated with every "->output." (Steve's suggestion will eliminate that concern). 4. Paragraph text is a little clumsy in Locale::Maketext. I'm tempted to TMPL_INCLUDE the fragments. But, I have to get creative to specify language-specific fragments because TMPL_INCLUDE doesn't handle a variable filename. To get around #1, I use Locale::Maketext in a way not intended. Instead of putting all my language into one "en_us" file within a single directory, I've created individual subdirectories mirroring the structure of the pages. In the lowest level is the "sitewide" language file. It is loaded and applied used for all pages for the site. Language files for specific pages are in subdirectories for each page. Like: /L10N/lang/sitewide/en-us /"/"/page-a/dynamic/en-us /"/"/page-a/static/en-us /"/"/page-a/sub-page-1/dynamic/en-us /"/"/page-a/sub-page-1/static/en-us /"/"/page-b/dynamic/en-us /"/"/page-b/static/en-us In my CGI::App "prerun" I detect the page hasn't been loaded yet, load the Locale::Maketext for sitewide (if it's not loaded yet), and the page-specific "static" language file. I can apply the page-specific static translation and discard the handle. I load the dynamic language file and keep that handle in a hash for as long as I keep the partially-processed page in a hash. This works really well for me. I only load as much Locale::Maktext lexicon as I need for a page. I only keep as much as I need for display-by-display changes. The "static" lexicons are dropped immediately after first-time use. The "sitewide" handle can be dropped when it is determined all the pages for a CGI::App module have been loaded. Because of the nature of Locale::Maketext's lexicon objects, I can put common language structures subroutines that can be required by all the lexicons that need it. One problem is that Locale::Maketext expects language lexicons in a single directory and uses them for determining what languages are available. So, I do my own negotiation of which language to use and call L::M's "->new" method instead of "->get_handle." I'm happier keeping the negitiation of language outside of L::M. This works really good! It's just the three things I mentioned regarding H::T. 1. It would work better if H::T had a way to perform an "internal_output" and replace its internal template with whatever the result of the "output" was (without eliminating empty tags). 2. If evaluations of TMPL_VARs could re-evaluate (maybe based upon an attribute on the VAR) so that a VAR containing a VAR could be processed. 3. If TMPL_INCLUDE was capable of resolving a filename like "my_text.<TMPL_VAR NAME=LANG>". This would let a single template be used for all languages. A lot of heavy lifting could be done as part of the page initialization. The page could then be minimally processed for as long as it remains in the cache (either H::T's or the application's own hash of templates like I'm doing). Steve's suggestion (to run a template through, then regex some munged tags) might be a good way to get around these things. Mark |
From: Mark F. <mar...@ea...> - 2005-01-24 17:51:56
|
Cees Hek provided me with a solution using H::T's filter. I think Sam suggested a filter first but, I didn't understand. (I would'nt have figured this out without seeing the sample Cees gave me.) It involves passing the language value to H::T's load_tmpl method. Then override H::T's _cache_key method to add the language value to the key H::T uses to cache the template. This lets you load a language-neutral template, update it for a language, and H::T will cache it uniquely by template-name and language. Another advantage is that I can generate all my pull-down (select/option) lists *in the filter* and they become permanent parts of the cached template -- not reevaluated with each output. When I create the string of select/option values, I add a "<TMPL_VAR NAME=form_control_name . $key>". Each time the page is reevaluated for output only this variable is evaluted (to replace it with "selected=selected"). This way the only variable template content is truly dynamic -- could change from display to display. It seems like this would be a significant performance boost. (I don't know.) I put most of the page-specific initialization like this into a module which is loaded when the filter fires. It gets called from the filter and performs the work. Because it's a module, variables/objects in the filter's namespace can be transferred into the module (by reference) using "$Static_page_init::{$page_name}::variable_name = $self->{my_db_handler}". References to logging subroutines can be passed in. (I do this just once after the module is loaded.) Another advantage to this modular design is that the language-translation handles, page initialization modules, etc., can be dropped after their use. Since it's one-time initialization there' no need to keep them around (using memory). Ultimately, I end up with a single language-translation handle used for dynamic (display to display) message translation. A very small lexicon for the page. I'm very happy with this solution. It does exactly what I was trying to do. And, makes selection/option lists more static like I'd always thought they should be. Below is a more detailed example with samples. It may be hard to follow since this complicated (and involves Locale::Maketext). I could create a small working example if people thought it would be useful. Thanks for everyone putting up with my ramblings! I hope this ends up being useful to someone else. ============== Detailed Example ============ The following describes a way to use H::T to 1. Perform efficient language translation upon templates 2. Minimize the number of duplicated templates which can result from supporting multiple languages (reducing the duplicated page structure and ongoing maintenance costs). 3. Cache templates unique by language -- not merely filename. 4. Cache templates in a manner that the bulk of language translation is cached and not re-evaluated with each display. 5. Use Locale::Maketext in a manner that lexicons are kept small and used as needed. 6. Avoid retention in memory of language translation objects/packages if, as stated in #4, the translation is one-time. 7. Use a language negotiation method other than Locale::Maketext (because the lexicons are used in a way that breaks Locale::Maketext's negotiation which is based upon a file structure it expects on disk). The example system uses CGI::App. But, this is not required. My CGI::App inherets from "superclassMyApp.pm" (which itself inherets from CGI::Application) containing the following pieces of code: 1. A subclass for H::T's _cache_key method. This method retrieves the "$language" value that was passed into H::T's load_tmp call. It uses the value (something like "en-US") to make the cached template unique by path, filename and language. ==============================>>> CUT HERE <<<================================== #*************************************************************************** **** # _cache_key # # subclass of H::T's method so we can cache templates by language. #*************************************************************************** **** sub _cache_key { my $self = shift; my $options = $self->{options}; return $self->SUPER::_cache_key().$options->{language}; } ==============================>>> CUT HERE <<<================================== 2. A method named "prepare_page_for_language" which is called from every C::A "run_mode" that displays a page. This method: 2a. Contains the following fragment of code to load the template. Notice the "language =>" parameter. That's what feeds the above-mentioned overriden _cache_key method. ==============================>>> CUT HERE <<<================================== #--------------------------------------------------------------------------- ---- # Load the template with site-wide and page-specific filter. #--------------------------------------------------------------------------- ---- $self->{template} = $self->load_tmpl($page . '.html', filter => [ { sub => $filter, format => 'scalar' }, ], cache => 1, double_file_cache => 1, file_cache_dir => '/tmp', language => $self->{session}->{LANGUAGE}, # pass this for the custom _cache_key method ); ==============================>>> CUT HERE <<<================================== 2b. The filter referred to by the above "load_tmpl." The filter is defined immediately prior to the above fragment. By creating the filter inside the same "prepare_page_for_language" method, the anonymous subroutine will be within scope of all the variables in the method. The goal of the filter is to initialize a template as much as possible so that needless H::T re-evaluation does not occur. So that the cached template's dynamic content is truly part of the display-by-display state change. In the case of language translation, reevaluating a template's static text for each display could be significant. The filter deals with three distinct preprocessing steps: 1. Language translation that occurs for all site's pages, where the content is static (header, footer, navigation bar text, title text, etc.). Once these items are generated they will not change for as long as the page is cached and redisplayed. 2. Language translation that occurs for a specific page being loaded, where the content is static (captions, form titles, sub-area navigation link text). Similar to site-wide text, this will not change for as long as the page is cached and redisplayed. This text only exists on this specific page. The translation handle won't be used for other pages (unlike the static site-wide handle). 3. Page initialization that occurs for the specific page, but does not necessarily involve language translation. For example, A "ROBOTS" "NOINDEX" might be set using a page-specific initialization routine since, once this is set it remains this value (like language translation) for as long as the page remains cached. This routine might generate select/option list values so that the only thing that has to be re-evaluated (by H::T) is the "selected" attribute (without having to redo TMPL_LOOPs). The goal is to eliminate (while we're going to the trouble) stuff that H::T would have to superflously re-evaluate for each display. The targets of these three preprocessing steps are represented by <LANG_SITE NAME=blah>, <LANG_PAGE NAME=blah> and <INIT_PAGE NAME=blah> tags. The filter follows: ==============================>>> CUT HERE <<<================================== #--------------------------------------------------------------------------- ---- # Subroutine used by H::T. Must be defined within this CGI::App method (in order # to be within scope of the variables it accesses). #--------------------------------------------------------------------------- ---- my $filter = sub { my $text_ref = shift; $filter_fired = 1; # so we know a page was loaded after tmpl_load (below) #--------------------------------------------------------------------------- -- # Load the sitewide language-translation handle for static content if it # hasn't already been loaded. (After all the pages are loaded for a CGI::App # module this handle is deleted.) #--------------------------------------------------------------------------- -- if (!exists($self->{LH}{'_sitewide'}{'_static'}{$self->{session}->{LANGUAGE}})) { $self->new_LH('_sitewide', '_static'); } #--------------------------------------------------------------------------- -- # Perform sitewide translation. For every LANG_SITE tag, send the value to the # sitewide language-translation handle (static content). #--------------------------------------------------------------------------- -- $$text_ref =~ s#<LANG_SITE +NAME\=([^>]*)>#$self->{LH}{'_sitewide'}{'_static'}{$self->{session}->{LANGU AGE}}->maketext($1)#eg; #--------------------------------------------------------------------------- -- # Load the page-specific language translation handle for static content. # Process all "LANG_PAGE" tags. Also load the page-specific module for # initializing a template in ways beyond "LANG_PAGE" translation. # # (We have to test if this is not already loaded because H::T calls the filter # multiple times when it loades a template). #--------------------------------------------------------------------------- -- if (!exists($self->{LH}{$page}{'_static'}{$self->{session}->{LANGUAGE}})) { $self->new_LH($page, '_static'); if ($perform_page_init) { my $module = 'Static_page_init::' . $page; eval "require $module"; } } #--------------------------------------------------------------------------- -- # Perform page-specific translation. For every LANG_PAGE tag, send the value # to the page-specific language-translation handle (static content). #--------------------------------------------------------------------------- -- $$text_ref =~ s#<LANG_PAGE +NAME\=([^>]*)>#$self->{LH}{$page}{'_static'}{$self->{session}->{LANGUAGE}}- >maketext($1)#eg; #--------------------------------------------------------------------------- -- # Call the page-specific initialization routine. #--------------------------------------------------------------------------- -- if ($perform_page_init) { no strict "refs"; &{'Static_page_init::' . $page . '::set_static_values'}($text_ref, $self->{LH}{$page}{'_static'}{$self->{session}->{LANGUAGE}}); use strict "refs"; } #--------------------------------------------------------------------------- -- # Perform common page initialization. # 1. Set the navigation bar's "selected". # 2. If the page is not "main", set the "NO" in front of "INDEX". (In the case # of "main" the tag will be stripped and the page will be indexed.) # 3. Eliminate any unset INIT_PAGE tags. #--------------------------------------------------------------------------- -- $$text_ref =~ s#<INIT_PAGE +NAME=$hdr_nav_selected># class="selected"#g; if ($page ne 'main') { $$text_ref =~ s#<INIT_PAGE +NAME=HDR_ROBOTS_INDEX>#NO#g; } $$text_ref =~ s#<INIT_PAGE +NAME\=[^>]+>##g; }; ==============================>>> CUT HERE <<<================================== The filter sets a variable to let me know it was executed by "H::T". It's the only way I can know after the 'tmpl_load' if H::T performed processing for a new template, or reused a cached copy. 2c. The following fragment of code is placed after "tmpl_load." If the filter was executed, this piece of code will - Get rid of the page-specific language handle (and package) (since neither are expected to be used again unless H::T senses that it needs to load a template again to replace a cached copy). - Adds a pagename to a hash so we can determine when all the pages a C::A module might display have been loaded (for a language). - Get rid of the site-wide language handle (and package) if all the pages have been loaded. - Loads the page-specific langauge handle for dynamic content (msgs, etc.) This is used as long as the page remains cached. ==============================>>> CUT HERE <<<================================== #--------------------------------------------------------------------------- ---- # After loading a page determine if the filter executed. If it did, perform # post-initialization processing. #--------------------------------------------------------------------------- ---- if ($filter_fired) { $filter_fired = 0; my $module = $self->{session}->{LANGUAGE}; $module =~ s/\-/_/; $module = lc($module); #--------------------------------------------------------------------------- -- # Delete the page-specific language-translation handle for static content, and # the module for page-specific initialization. After a page is loaded and # cached these aren't used any longer. #--------------------------------------------------------------------------- -- delete($self->{LH}{$page}{'_static'}{$self->{session}->{LANGUAGE}}); delete_package('lang::' . $page . '::_static::' . $module); delete_package('Static_page_init::' . $page); #--------------------------------------------------------------------------- -- # Add the page-name to a hash of page-names known to have been loaded. #--------------------------------------------------------------------------- -- ${$loaded_pages{$self->{session}->{LANGUAGE}}}{$page} = 1; #--------------------------------------------------------------------------- -- # If the total number of pages displayable have been loaded, delete the site- # wide language-translation handle for static content. After all pages are # loaded and cached, this isn't needed any longer (unless H::T refreshes # the cache, when it will be reloaded in the filter and deleted here, again.) #--------------------------------------------------------------------------- -- if (keys %{$loaded_pages{$self->{session}->{LANGUAGE}}} == $max_pages) { delete($self->{LH}{'_sitewide'}{'_static'}{$self->{session}->{LANGUAGE}}); delete_package('lang::_sitewide::_static::' . $module); } #--------------------------------------------------------------------------- -- # Load the page's language-translation handle for dynamic content. We keep # this for each redisplay. (Test if it's already loaded. The cached template # may have been refreshed causing the filter to fire. No need to reload the # handle). #--------------------------------------------------------------------------- -- if (!exists($self->{LH}{$page}{'_dynamic'}{$self->{session}->{LANGUAGE}})) { $self->new_LH($page, '_dynamic'); } } # end filter fired ==============================>>> CUT HERE <<<================================== That's all there is to it. For additional clarification 1. The superclassMyApp.pm (which my C::A modules inheret from) contains: - The following relevant statements: use base 'CGI::Application'; use strict; use Symbol qw(delete_package); - The following "our" variables: our ($max_pages, $perform_page_init, $hdr_nav_selected); our (%loaded_pages); Those variables are filled in by subclassing C::A's "cgiapp_init" and calling it with cgiapp_init(2, 0, 'HDR_NAV_MAIN'); - The prepare_page_for_language method, which is called from any C::A runmode as: $self->prepare_page_for_language('main'); where 'main' is the page to load. 2. The filter checks if the language handles have been created before creating them. It may not be intuitive, but H::T executes the filter multiple times when it loads the template. Therefore, it can't be assumed that the filter is being exeucted for the first or last time (for a page). The conditional is used to determine if it's necessary to load the language handles. And, the "filter_fired" variable is used to determine afterwards if anything happened (so cleanup can occur). 3. The subroutine to create language handles (referenced throughout the above sample code) looks like the following. It is contained within superclassMyApp.pm. It loads Locale::Maketext language handles in a more granular manner than L::M expects. For this reason, it loads the package and performs a "new" instead of "get_handle" (which performs language negotiation. I use use "I18N::AcceptLanguage" for that.) ==============================>>> CUT HERE <<<================================== #*************************************************************************** **** # new_LH # # Common process to create a Locale::Maketext handle. We create 'sitewide', # 'page::_static' and 'page::_dynamic'. We use Maketext's "->new" method because # it has an inefficient language negotiation feature. #*************************************************************************** **** sub new_LH { my $self = shift; my ($page, $type) = @_; # Start: use maketext's -> new method (bypass negotiation) my $module = $self->{session}->{LANGUAGE}; $module =~ s/\-/_/; $module = 'lang::' . $page . '::' . $type . '::' . lc($module); eval "require $module"; $self->{LH}{$page}{$type}{$self->{session}->{LANGUAGE}} = $module->new(); # End: use maketext's -> new method (bypass negotiation) return; } ==============================>>> CUT HERE <<<================================== - The language modules (lexicons) are in a directory structure as follows: ~/lang/_sitewide/_static/en_us ~/lang/main/_static/en_us ~/lang/main/_dynamic/en_us ~/lang/main/help/_static/en_us # examples of lexicons for sub-pages, in ~/lang/main/help/_dynamic/en_us # which case $page is "main::help" Common translation materials can be shared by using something like this in a language module (lexicon) $shared_sidenav = do '/home/fm/bin/lang/profile/static/shared/sidenav/en_us.include'; Which refers to a file containing: ==============================>>> CUT HERE <<<================================== { sidenav_text => (['Area 1', 'Area 2', 'Area 3', 'Area 4', 'Area 5', 'Area 6', 'Area 7']), sidenav_link_title => (['Go to area 1.', 'Go to area 2.', 'Go to area 3.', 'Go to area 4.', 'Go to area 5.', 'Go to area 6.', 'Go to area 7.']) } ==============================>>> CUT HERE <<<================================== The lexicon will then relate these two tags: <LANG_PAGE NAME=SIDENAV_TEXT> <LANG_PAGE NAME=SIDENAV_TEXT> to text this way: 'SIDENAV_TEXT' => ${$shared_sidenav}{sidenav_text}, 'SIDENAV_LINK_TITLE' => ${$shared_sidenav}{sidenav_link_title}, And return an array ref of the same 7 items through different lexicons (specific to different pages where the sidenav text is needed). 4. The page-specific initialization modules are stored in the file location such as: ~/Static_page_init/main.pm ~/Static_page_init/main/help.pm # an example of a sub-page Common processing (like generating the side-navigation links) can be shared by required libraries. === end |