File Coverage

blib/lib/Template/Multilingual/Parser.pm
Criterion Covered Total %
statement 56 56 100.0
branch 12 14 85.7
condition 3 5 60.0
subroutine 6 6 100.0
pod 3 3 100.0
total 80 84 95.2


line stmt bran cond sub pod time code
1             package Template::Multilingual::Parser;
2              
3 3     3   29 use strict;
  3         8  
  3         164  
4 3     3   17 use base qw(Template::Parser);
  3         8  
  3         3887  
5              
6             our $VERSION = '1.00';
7              
8             sub new
9             {
10 4     4 1 10 my ($class, $options) = @_;
11 4         42 my $self = $class->SUPER::new($options);
12 4         784 $self->{_sections} = [];
13 4   50     73 $self->{_langvar} = $options->{LANGUAGE_VAR} || 'language';
14              
15 4         12 my $style = $self->{ STYLE }->[-1];
16 4         20 @$self{ qw(_start _end) } = @$style{ qw( START_TAG END_TAG ) };
17 4         13 for (qw( _start _end )) {
18 8         59 $self->{$_} =~ s/\\([^\\])/$1/g;
19             }
20              
21 4         20 return $self;
22             }
23              
24             sub parse
25             {
26 30     30 1 6444 my ($self, $text) = @_;
27              
28             # isolate multilingual sections
29 30         234 $self->_tokenize($text);
30              
31             # replace multilingual sections with TT directives
32 30         415 my ($S, $E, $LANGVAR) = map $self->{$_}, qw(_start _end _langvar);
33              
34             # if language is a variant (en_US), create a template variable holding the fallback value (en)
35 30         245 $text = "$S IF (tm_matches = $LANGVAR.match('^(\\w+)[-_].*\$')); tm_fb = tm_matches.0; END $E";
36              
37 30         68 for my $section (@{$self->{_sections}}) {
  30         89  
38 34 100       177 if ($section->{nolang}) {
    50          
39 8         21 $text .= $section->{nolang};
40             }
41             elsif (my $t = $section->{lang}) {
42 26         98 my @languages = keys %$t;
43              
44             # first loop through languages: look for exact match
45 26         86 $text .= "$S tm_f = 0; SWITCH $LANGVAR $E";
46 26         111 for my $lang (@languages) {
47 44         175 $text .= "$S CASE '$lang' $E" . $t->{$lang};
48             }
49             # add a default case to trigger fallback
50 26         69 $text .= "$S CASE; tm_f=1; END; $E";
51              
52             # second loop: fallback to primary language (en_US matches en)
53 26         65 $text .= "$S IF tm_fb AND tm_f; tm_f=0; SWITCH tm_fb; $E";
54 26         134 for my $lang (@languages) {
55 44         458 $text .= "$S CASE '$lang' $E" . $t->{$lang};
56             }
57             # add a default case to trigger last resort fallback
58             # LANG is fr_XX or fr but template has neither
59             # we try to fallback to fr_YY is present
60 26         39 my %seen;
61 26 100 66     184 my @fallbacks = map { /^(\w+)[-_].*$/ && !$seen{$_}++ ? [ $1 => $_] : () } sort @languages;
  44         446  
62 26 100       80 if (@fallbacks) {
63             # third loop: fallback to first available variant
64 12         39 $text .= "$S CASE; tm_f=1; END; END; IF tm_f; SWITCH tm_fb || $LANGVAR; $E";
65 12         25 for my $ref (@fallbacks) {
66 20         39 my ($lang, $variant) = @$ref;
67 20         154 $text .= "$S CASE '$lang' $E" . $t->{$variant};
68             }
69             }
70 26         129 $text .= "$S END; END $E";
71             }
72             }
73 30         210 return $self->SUPER::parse ($text);
74             }
75              
76             sub _tokenize
77             {
78 30     30   69 my ($self, $text) = @_;
79              
80             # extract all sections from the text
81 30         91 $self->{_sections} = [];
82 30         379 my @tokens = split m!(.*?)!s, $text;
83 30         127 my $i = 0;
84 30         81 for my $t (@tokens) {
85 56 100       144 if ($i) { # ... multilingual section
86 26         139 my %section;
87 26         228 while ($t =~ m!<([^<>]+)>(.*?)!gs) {
88 44         740 $section{$1} = $2;
89             }
90 26 50       277 push @{$self->{_sections}}, { lang => \%section }
  26         138  
91             if %section;
92             }
93             else { # bare text
94 30 100       101 push @{$self->{_sections}}, { nolang => $t } if $t;
  8         57  
95             }
96 56         297 $i = 1 - $i;
97             }
98             }
99 30     30 1 541863 sub sections { $_[0]->{_sections} }
100              
101             =head1 NAME
102              
103             Template::Multilingual::Parser - Multilingual template parser
104              
105             =head1 SYNOPSIS
106              
107             use Template;
108             use Template::Multilingual::Parser;
109            
110             my $parser = Template::Multilingual::Parser->new();
111             my $template = Template->new(PARSER => $parser);
112             $template->process('example.ttml', { language => 'en'});
113              
114             =head1 DESCRIPTION
115              
116             This subclass of Template Toolkit's C parses multilingual
117             templates: templates that contain text in several languages.
118              
119            
120             Hello!
121             Bonjour !
122            
123              
124             Use this module directly if you have subclassed C