use HTML::Parser (); ...; sub translate_template { my $self = shift; my ( $lang, $template ) = @_; my ( $langlong, $langshort ) = ( $lang =~ m/(([^-]+)(?:-.+)?)/ ); my $replaces = { 'langshort' => $langshort, 'langlong' => $langlong, 'langdir' => 'ltr', 'countries' => $self->countries_menu($lang), }; if ( $self->{'locales'}{$lang}->get_character_orientation_from_code() eq 'right-to-left' ) { $replaces->{'langdir'} = 'rtl'; } foreach my $replace ( keys %$replaces ) { $template =~ s/!!\$$replace/$replaces->{$replace}/g; } my $p = HTML::Parser->new( api_version => 3, default_h => [ \&translate_template_parser, 'event,tagname,attr,attrseq,text,dtext,self' ], unbroken_text => 1, xml_pic => 1, ); $p->{'catalog'} = $self->{'catalog'}; $p->{'lang'} = $lang; $p->{'return'} = ''; $p->parse($template); $p->eof; $template = $p->{'return'}; return $template; } sub translate_template_parser { state $closed_tags = set( 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr' ); state $depth = 0; state $intransl = 0; state $buf = ''; my ( $event, $tag, $attr, $attrseq, $text, $dtext, $obj ) = @_; # pre-closed tags shouldn't increase or decrease our depth if ( $closed_tags->member($tag) ) { $event = 'closed'; } if ( defined($attrseq) and scalar(@$attrseq) and ( $attrseq->[ -1 ] eq '/' ) ) { $event = 'closed'; } my $append_text = $dtext // $text; if ( $event eq 'start' ) { ## go down a level $buf .= $append_text; if ( $attr->{'data-gettext'} ) { $obj->{'return'} .= $buf; $buf = ''; $intransl = $depth; } $depth += 1; } elsif ( $event eq 'end' ) { ## come up a level, appending translated text $depth -= 1; if ( $intransl == $depth ) { for ($buf) { s/\A\s+//; s/\s+\z//; } $obj->{'return'} .= $obj->{'catalog'}->lookup( $obj->{'lang'}, $buf )->msgstr; $buf = ''; $intransl = 0; } $buf .= $append_text; } elsif ( $event eq 'end_document' ) { $obj->{'return'} .= $buf; } else { $buf .= $append_text; } } ...;