File Coverage

blib/lib/App/Greple/xlate.pm
Criterion Covered Total %
statement 117 151 77.4
branch 34 76 44.7
condition 2 5 40.0
subroutine 23 26 88.4
pod 1 12 8.3
total 177 270 65.5


line stmt bran cond sub pod time code
1             package App::Greple::xlate;
2              
3             our $VERSION = "1.01";
4              
5             =encoding utf-8
6              
7             =head1 NAME
8              
9             App::Greple::xlate - translation support module for greple
10              
11             =head1 SYNOPSIS
12              
13             greple -Mxlate::deepl --xlate pattern target-file
14              
15             greple -Mxlate::gpt4 --xlate pattern target-file
16              
17             greple -Mxlate::gpt5 --xlate pattern target-file
18              
19             greple -Mxlate --xlate-engine gpt5 --xlate pattern target-file
20              
21             =head1 VERSION
22              
23             Version 1.01
24              
25             =head1 DESCRIPTION
26              
27             B B module find desired text blocks and replace them by
28             the translated text. Currently DeepL (F), ChatGPT 4.1
29             (F), and GPT-5 (F) module are implemented as a back-end engine.
30              
31             If you want to translate normal text blocks in a document written in
32             the Perl's pod style, use B command with C and
33             C module like this:
34              
35             greple -Mxlate::deepl -Mperl --pod --re '^([\w\pP].*\n)+' --all foo.pm
36              
37             In this command, pattern string C<^([\w\pP].*\n)+> means consecutive
38             lines starting with alpha-numeric and punctuation letter. This
39             command show the area to be translated highlighted. Option B<--all>
40             is used to produce entire text.
41              
42             =for html

43            
44            

45              
46             Then add C<--xlate> option to translate the selected area. Then, it
47             will find the desired sections and replace them by the B
48             command output.
49              
50             By default, original and translated text is printed in the "conflict
51             marker" format compatible with L. Using C format, you
52             can get desired part by L command easily. Output format
53             can be specified by B<--xlate-format> option.
54              
55             =for html

56            
57            

58              
59             If you want to translate entire text, use B<--match-all> option. This
60             is a short-cut to specify the pattern C<(?s).+> which matches entire
61             text.
62              
63             Conflict marker format data can be viewed in side-by-side style by
64             L command with C<-V> option. Since it makes no sense
65             to compare on a per-string basis, the C<--no-cdif> option is
66             recommended. If you do not need to color the text, specify
67             C<--no-textcolor> (or C<--no-tc>).
68              
69             sdif -V --no-filename --no-tc --no-cdif data_shishin.deepl-EN-US.cm
70              
71             =for html

72            
73            

74              
75             =head1 NORMALIZATION
76              
77             Processing is done in specified units, but in the case of a sequence
78             of multiple lines of non-empty text, they are converted together into
79             a single line. This operation is performed as follows:
80              
81             =over 2
82              
83             =item *
84              
85             Remove white space at the beginning and end of each line.
86              
87             =item *
88              
89             If a line ends with a full-width punctuation character, concatenate
90             with next line.
91              
92             =item *
93              
94             If a line ends with a full-width character and the next line begins
95             with a full-width character, concatenate the lines.
96              
97             =item *
98              
99             If either the end or the beginning of a line is not a full-width
100             character, concatenate them by inserting a space character.
101              
102             =back
103              
104             Cache data is managed based on the normalized text, so even if
105             modifications are made that do not affect the normalization results,
106             the cached translation data will still be effective.
107              
108             This normalization process is performed only for the first (0th) and
109             even-numbered pattern. Thus, if two patterns are specified as
110             follows, the text matching the first pattern will be processed after
111             normalization, and no normalization process will be performed on the
112             text matching the second pattern.
113              
114             greple -Mxlate -E normalized -E not-normalized
115              
116             Therefore, use the first pattern for text that is to be processed by
117             combining multiple lines into a single line, and use the second
118             pattern for pre-formatted text. If there is no text to match in the
119             first pattern, use a pattern that does not match anything, such as
120             C<(?!)>.
121              
122             =head1 MASKING
123              
124             Occasionally, there are parts of text that you do not want translated.
125             For example, tags in markdown files. DeepL suggests that in such
126             cases, the part of the text to be excluded be converted to XML tags,
127             translated, and then restored after the translation is complete. To
128             support this, it is possible to specify the parts to be masked from
129             translation.
130              
131             --xlate-setopt maskfile=MASKPATTERN
132              
133             This will interpret each line of the file C as a regular
134             expression, translate strings matching it, and revert after
135             processing. Lines beginning with C<#> are ignored.
136              
137             Complex pattern can be written on multiple lines with backslash
138             escpaed newline.
139              
140             How the text is transformed by masking can be seen by B<--xlate-mask>
141             option.
142              
143             This interface is experimental and subject to change in the future.
144              
145             =head1 OPTIONS
146              
147             =over 7
148              
149             =item B<--xlate>
150              
151             =item B<--xlate-color>
152              
153             =item B<--xlate-fold>
154              
155             =item B<--xlate-fold-width>=I (Default: 70)
156              
157             Invoke the translation process for each matched area.
158              
159             Without this option, B behaves as a normal search command. So
160             you can check which part of the file will be subject of the
161             translation before invoking actual work.
162              
163             Command result goes to standard out, so redirect to file if necessary,
164             or consider to use L module.
165              
166             Option B<--xlate> calls B<--xlate-color> option with B<--color=never>
167             option.
168              
169             With B<--xlate-fold> option, converted text is folded by the specified
170             width. Default width is 70 and can be set by B<--xlate-fold-width>
171             option. Four columns are reserved for run-in operation, so each line
172             could hold 74 characters at most.
173              
174             =item B<--xlate-engine>=I
175              
176             Specifies the translation engine to be used. If you specify the engine
177             module directly, such as C<-Mxlate::deepl>, you do not need to use
178             this option.
179              
180             At this time, the following engines are available
181              
182             =over 2
183              
184             =item * B: DeepL API
185              
186             =item * B: gpt-3.5-turbo
187              
188             =item * B: gpt-4.1
189              
190             =item * B: gpt-4o-mini
191              
192             B's interface is unstable and cannot be guaranteed to work
193             correctly at the moment.
194              
195             =item * B: gpt-5
196              
197             =back
198              
199             =item B<--xlate-labor>
200              
201             =item B<--xlabor>
202              
203             Instead of calling translation engine, you are expected to work for.
204             After preparing text to be translated, they are copied to the
205             clipboard. You are expected to paste them to the form, copy the
206             result to the clipboard, and hit return.
207              
208             =item B<--xlate-to> (Default: C)
209              
210             Specify the target language. You can get available languages by
211             C command when using B engine.
212              
213             =item B<--xlate-format>=I (Default: C)
214              
215             Specify the output format for original and translated text.
216              
217             The following formats other than C assume that the part to be
218             translated is a collection of lines. In fact, it is possible to
219             translate only a portion of a line, but specifying a format other than
220             C will not produce meaningful results.
221              
222             =over 4
223              
224             =item B, B
225              
226             Original and converted text are printed in L conflict marker
227             format.
228              
229             <<<<<<< ORIGINAL
230             original text
231             =======
232             translated Japanese text
233             >>>>>>> JA
234              
235             You can recover the original file by next L command.
236              
237             sed -e '/^<<<<<<< /d' -e '/^=======$/,/^>>>>>>> /d'
238              
239             =item B, I<:::::::>
240              
241             The original and translated text are output in a markdown's custom
242             container style.
243              
244             ::::::: ORIGINAL
245             original text
246             :::::::
247             ::::::: JA
248             translated Japanese text
249             :::::::
250              
251             Above text will be translated to the following in HTML.
252              
253            
254             original text
255            
256            
257             translated Japanese text
258            
259              
260             Number of colon is 7 by default. If you specify colon sequence like
261             C<:::::>, it is used instead of 7 colons.
262              
263             =item B
264              
265             Original and converted text are printed in L C<#ifdef>
266             format.
267              
268             #ifdef ORIGINAL
269             original text
270             #endif
271             #ifdef JA
272             translated Japanese text
273             #endif
274              
275             You can retrieve only Japanese text by the B command:
276              
277             unifdef -UORIGINAL -DJA foo.ja.pm
278              
279             =item B
280              
281             =item B
282              
283             Original and converted text are printed separated by single blank
284             line. For C, it also outputs a newline after the converted
285             text.
286              
287             =item B
288              
289             If the format is C (translated text) or unkown, only translated
290             text is printed.
291              
292             =back
293              
294             =item B<--xlate-maxlen>=I (Default: 0)
295              
296             Specify the maximum length of text to be sent to the API at once.
297             Default value is set as for free DeepL account service: 128K for the
298             API (B<--xlate>) and 5000 for the clipboard interface
299             (B<--xlate-labor>). You may be able to change these value if you are
300             using Pro service.
301              
302             =item B<--xlate-maxline>=I (Default: 0)
303              
304             Specify the maximum lines of text to be sent to the API at once.
305              
306             Set this value to 1 if you want to translate one line at a time. This
307             option takes precedence over the C<--xlate-maxlen> option.
308              
309             =item B<--xlate-prompt>=I
310              
311             Specify a custom prompt to be sent to the translation engine. This option
312             is only available when using ChatGPT engines (gpt3, gpt4, gpt4o). You can
313             customize the translation behavior by providing specific instructions to the
314             AI model. If the prompt contains C<%s>, it will be replaced with the target
315             language name.
316              
317             =item B<--xlate-context>=I
318              
319             Specify additional context information to be sent to the translation
320             engine. This option can be used multiple times to provide multiple
321             context strings. The context information helps the translation engine
322             understand the background and produce more accurate translations.
323              
324             =item B<--xlate-glossary>=I
325              
326             Specify a glossary ID to be used for translation. This option is only
327             available when using the DeepL engine. The glossary ID should be obtained
328             from your DeepL account and ensures consistent translation of specific terms.
329              
330             =item B<-->[B]B (Default: True)
331              
332             See the tranlsation result in real time in the STDERR output.
333              
334             =item B<--xlate-stripe>
335              
336             Use L module to show the matched part by zebra
337             striping fashion. This is useful when the matched parts are connected
338             back-to-back.
339              
340             The color palette is switched according to the background color of the
341             terminal. If you want to specify explicitly, you can use
342             B<--xlate-stripe-light> or B<--xlate-stripe-dark>.
343              
344             =item B<--xlate-mask>
345              
346             Perform masking function and display the converted text as is without
347             restoration.
348              
349             =item B<--match-all>
350              
351             Set the whole text of the file as a target area.
352              
353             =item B<--lineify-cm>
354              
355             =item B<--lineify-colon>
356              
357             In the case of the C and C formats, the output is split and
358             formatted line by line. Therefore, if only a portion of a line is to
359             be translated, the expected result cannot be obtained. These filters
360             fix output that is corrupted by translating part of a line into normal
361             line-by-line output.
362              
363             In the current implementation, if multiple parts of a line are
364             translated, they are output as independent lines.
365              
366             =back
367              
368             =head1 CACHE OPTIONS
369              
370             B module can store cached text of translation for each file and
371             read it before execution to eliminate the overhead of asking to
372             server. With the default cache strategy C, it maintains cache
373             data only when the cache file exists for target file.
374              
375             Use B<--xlate-cache=clear> to initiate cache management or to clean up
376             all existing cache data. Once executed with this option, a new cache
377             file will be created if one does not exist and then automatically
378             maintained afterward.
379              
380             =over 7
381              
382             =item --xlate-cache=I
383              
384             =over 4
385              
386             =item C (Default)
387              
388             Maintain the cache file if it exists.
389              
390             =item C
391              
392             Create empty cache file and exit.
393              
394             =item C, C, C<1>
395              
396             Maintain cache anyway as far as the target is normal file.
397              
398             =item C
399              
400             Clear the cache data first.
401              
402             =item C, C, C<0>
403              
404             Never use cache file even if it exists.
405              
406             =item C
407              
408             By default behavior, unused data is removed from the cache file. If
409             you don't want to remove them and keep in the file, use C.
410              
411             =back
412              
413             =item B<--xlate-update>
414              
415             This option forces to update cache file even if it is not necessary.
416              
417             =back
418              
419             =head1 COMMAND LINE INTERFACE
420              
421             You can easily use this module from the command line by using the
422             C command included in the distribution. See the C man
423             page for usage.
424              
425             The C command supports GNU-style long options such as
426             C<--to-lang>, C<--from-lang>, C<--engine>, and C<--file>. Use
427             C to see all available options.
428              
429             The C command works in concert with the Docker environment, so
430             even if you do not have anything installed on hand, you can use it as
431             long as Docker is available. Use C<-D> or C<-C> option.
432              
433             Docker operations are handled by L, which can also be
434             used as a standalone command. The C command supports the
435             C<.dozorc> configuration file for persistent container settings.
436              
437             Also, since makefiles for various document styles are provided,
438             translation into other languages is possible without special
439             specification. Use C<-M> option.
440              
441             You can also combine the Docker and C options so that you can
442             run C in a Docker environment.
443              
444             Running like C will launch a shell with the current working
445             git repository mounted.
446              
447             Read Japanese article in L section for detail.
448              
449             =head1 EMACS
450              
451             Load the F file included in the repository to use C
452             command from Emacs editor. C function translate the
453             given region. Default language is C and you can specify
454             language invoking it with prefix argument.
455              
456             =for html

457            
458            

459              
460             =head1 ENVIRONMENT
461              
462             =over 7
463              
464             =item DEEPL_AUTH_KEY
465              
466             Set your authentication key for DeepL service.
467              
468             =item OPENAI_API_KEY
469              
470             OpenAI authentication key.
471              
472             =back
473              
474             =head1 INSTALL
475              
476             =head2 CPANMINUS
477              
478             $ cpanm App::Greple::xlate
479              
480             =head2 TOOLS
481              
482             You have to install command line tools for DeepL and ChatGPT.
483              
484             L
485              
486             L
487              
488             =head1 SEE ALSO
489              
490             =head2 MODULES
491              
492             L,
493             L,
494             L
495              
496             L - Generic Docker runner used by xlate for container operations
497              
498             =head2 RELATED MODULES
499              
500             =over 2
501              
502             =item * L
503              
504             See the B manual for the detail about target text pattern.
505             Use B<--inside>, B<--outside>, B<--include>, B<--exclude> options to
506             limit the matching area.
507              
508             =item * L
509              
510             You can use C<-Mupdate> module to modify files by the result of
511             B command.
512              
513             =item * L
514              
515             Use B to show conflict marker format side by side with B<-V>
516             option.
517              
518             =item * L
519              
520             Greple B module use by B<--xlate-stripe> option.
521              
522             =back
523              
524             =head2 RESOURCES
525              
526             =over 2
527              
528             =item * L
529              
530             Docker container image.
531              
532             =item * L
533              
534             The C library used for option parsing in the C
535             script and L.
536              
537             =item * L
538              
539             DeepL Python library and CLI command.
540              
541             =item * L
542              
543             OpenAI Python Library
544              
545             =item * L
546              
547             OpenAI command line interface
548              
549             =back
550              
551             =head2 ARTICLES
552              
553             =over 2
554              
555             =item * L
556              
557             Greple module to translate and replace only the necessary parts with DeepL API (in Japanese)
558              
559             =item * L
560              
561             Generating documents in 15 languages with DeepL API module (in Japanese)
562              
563             =item * L
564              
565             Automatic translation Docker environment with DeepL API (in Japanese)
566              
567             =back
568              
569             =head1 AUTHOR
570              
571             Kazumasa Utashiro
572              
573             =head1 LICENSE
574              
575             Copyright © 2023-2026 Kazumasa Utashiro.
576              
577             This library is free software; you can redistribute it and/or modify
578             it under the same terms as Perl itself.
579              
580             =cut
581              
582 11     11   435529 use v5.26;
  11         51  
583 11     11   67 use warnings;
  11         21  
  11         8862  
584 11     11   955 use utf8;
  11         492  
  11         174  
585              
586 11     11   1416 use Data::Dumper;
  11         10938  
  11         1173  
587              
588 11     11   7491 use Text::ANSI::Fold ':constants';
  11         687155  
  11         2299  
589 11     11   7376 use Command::Run;
  11         147226  
  11         584  
590 11     11   850 use Hash::Util qw(lock_keys);
  11         4802  
  11         110  
591 11     11   875 use Unicode::EastAsianWidth;
  11         21  
  11         939  
592 11     11   119 use List::Util qw(max);
  11         25  
  11         834  
593              
594 11     11   70 use Exporter 'import';
  11         24  
  11         9354  
595             our @EXPORT_OK = qw($VERSION &opt %opt);
596             our @EXPORT_TAGS = ( all => [ qw($VERSION) ] );
597              
598             our %opt = (
599             debug => \(our $debug = 0),
600             engine => \(our $xlate_engine),
601             progress => \(our $show_progress = 1),
602             format => \(our $output_format = 'conflict'),
603             collapse => \(our $collapse_spaces = 1),
604             from => \(our $lang_from = 'ORIGINAL'),
605             to => \(our $lang_to = 'EN-US'),
606             fold => \(our $fold_line = 0),
607             width => \(our $fold_width = 70),
608             auth_key => \(our $auth_key),
609             method => \(our $cache_method //= $ENV{GREPLE_XLATE_CACHE} || 'auto'),
610             update => \(our $force_update = 0),
611             dryrun => \(our $dryrun = 0),
612             maxlen => \(our $max_length = 0),
613             maxline => \(our $max_line = 0),
614             prompt => \(our $prompt),
615             mask => \(our $mask),
616             maskfile => \(our $maskfile),
617             glossary => \(our $glossary),
618             contexts => (\our @contexts),
619             );
620             lock_keys %opt;
621 32     32 0 56 sub opt :lvalue { ${$opt{+shift}} }
  32         160  
622              
623             my $current_file;
624             my $colon_count = 7;
625              
626             our %formatter = (
627             xtxt => undef,
628             none => undef,
629             conflict => sub {
630             join '',
631             "<<<<<<< $lang_from\n",
632             $_[0],
633             "=======\n",
634             $_[1],
635             ">>>>>>> $lang_to\n";
636             },
637             cm => 'conflict',
638             colon => sub {
639             my $colon = ':' x $colon_count;
640             join '',
641             "$colon $lang_from\n",
642             $_[0],
643             "$colon\n",
644             "$colon $lang_to\n",
645             $_[1],
646             "$colon\n";
647             },
648             ifdef => sub {
649             join '',
650             "#ifdef $lang_from\n",
651             $_[0],
652             "#endif\n",
653             "#ifdef $lang_to\n",
654             $_[1],
655             "#endif\n";
656             },
657             space => sub { join("\n", @_) },
658             'space+' => sub { join("\n", @_) . "\n" },
659             discard => sub { '' },
660             );
661              
662             # aliases
663             for (keys %formatter) {
664             next if ! $formatter{$_} or ref $formatter{$_};
665             $formatter{$_} = $formatter{$formatter{$_}} // die;
666             }
667              
668             my %cache;
669              
670 11     11   12923 use App::Greple::xlate::Mask;
  11         172  
  11         2660  
671             my $maskobj;
672              
673             sub setup {
674 8 50   8 0 34 return if state $once_called++;
675 8 50       30 if (defined $cache_method) {
676 8 50       30 if ($cache_method eq '') {
677 0         0 $cache_method = 'auto';
678             }
679 8 50       61 if ($cache_method =~ /^(no|never)/i) {
680 0         0 $cache_method = '';
681             }
682             }
683 8 50       28 if ($xlate_engine) {
684 8         27 my $mod = __PACKAGE__ . "::$xlate_engine";
685 8 50       888 if (eval "require $mod") {
686 8         139 $mod->import;
687             } else {
688 0         0 die "Engine $xlate_engine is not available.\n";
689             }
690 11     11   191 no strict 'refs';
  11         29  
  11         3236  
691 8         39 ${"$mod\::lang_from"} = $lang_from;
  8         47  
692 8         21 ${"$mod\::lang_to"} = $lang_to;
  8         34  
693 8         15 *XLATE = \&{"$mod\::xlate"};
  8         43  
694 8 50       39 if (not defined &XLATE) {
695 0         0 die "No \"xlate\" function in $mod.\n";
696             }
697             }
698 8 50       35 if (my $pat = opt('mask')) {
699 0         0 $maskobj = App::Greple::xlate::Mask->new(pattern => $pat);
700             }
701 8 50       28 if (my $patfile = opt('maskfile')) {
702 0         0 $maskobj = App::Greple::xlate::Mask->new(file => $patfile);
703             }
704             }
705              
706 11     11   12739 use App::Greple::xlate::Text;
  11         39  
  11         21143  
707              
708             sub postgrep {
709 8     8 0 227 my $grep = shift;
710 8         23 my @miss;
711 8         131 for my $r ($grep->result) {
712 30         125 my($b, @match) = @$r;
713 30         47 for my $m (@match) {
714 30         47 my($s, $e, $i) = @$m;
715 30         137 my $key = App::Greple::xlate::Text
716             ->new($grep->cut(@$m), paragraph => ($i % 2 == 0))
717             ->normalized;
718 30 100       183 if (not exists $cache{$key}) {
719 29         1983 $cache{$key} = undef;
720 29         3797 push @miss, $key;
721             }
722             }
723             }
724 8 50       2008 cache_update(@miss) if @miss;
725             }
726              
727             sub _progress {
728 16 50   16   68 my $opt = ref $_[0] ? shift : {};
729 16 50       49 opt('progress') or return;
730 16 50       76 if (my $label = $opt->{label}) { print STDERR "[xlate.pm] $label:\n" }
  16         84  
731 16         85 for (my @s = @_) {
732 58         147 my $i =()= /^/mg;
733 58 50       123 my @m = ($i == 1 ? '╶' : '│') x $i ;
734 58 50       130 @m[0,-1] = qw(┌ └) if $i > 1;
735 58         129 s/^/sprintf "%7s ", shift(@m)/mge;
  58         223  
736 58         201 s/(?
737 58         211 s/( +)$/"␣" x length($1)/mge;
  0         0  
738 58         175 print STDERR $_;
739             }
740             }
741              
742             sub cache_update {
743 8     8 0 158 binmode STDERR, ':encoding(utf8)';
744              
745 8         514 my @from = @_;
746 8         48 _progress({label => "From"}, @from);
747 8 50       32 return @from if $dryrun;
748              
749 8 50       24 $maskobj->mask(@from) if $maskobj;
750 8         31 my @chop = grep { $from[$_] =~ s/(?
  29         83  
751 8         53 my @to = map { s/ +$//mgr } &XLATE(@from);
  29         72  
752 8         33 chop @to[@chop];
753 8 50       40 $maskobj->unmask(@to)->reset if $maskobj;
754              
755 8         39 _progress({label => "To"}, @to);
756 8 50       43 die "Unmatched response:\n@to" if @from != @to;
757 8         68 @cache{@_} = @to;
758             }
759              
760             sub fold_lines {
761 0     0 0 0 state $fold = Text::ANSI::Fold->new(
762             width => $fold_width,
763             boundary => 'word',
764             linebreak => LINEBREAK_ALL,
765             runin => 4,
766             runout => 4,
767             );
768 0         0 local $_ = shift;
769 0         0 s/(.+)/join "\n", $fold->text($1)->chops/ge;
  0         0  
770 0         0 $_;
771             }
772              
773             sub xlate {
774 30     30 1 130 my $param = { @_ };
775 30         95 my($index, $text) = $param->@{qw(index match)};
776 30         96 my $obj = App::Greple::xlate::Text->new($text,
777             paragraph => ($index % 2 == 0));
778 30   50     104 my $s = $cache{$obj->normalized} // "!!! TRANSLATION ERROR !!!\n";
779 30         87 $obj->unstrip($s);
780 30 50       75 $s = fold_lines $s if $fold_line;
781 30 100       72 if (state $formatter = $formatter{$output_format}) {
782 28         68 return $formatter->($text, $s);
783             } else {
784 2         35 return $s;
785             }
786             }
787 30     30 0 346 sub callback { goto &xlate }
788              
789             sub mask_string {
790 0     0 0 0 my($s) = +{ @_ }->{match};
791 0 0       0 if ($maskobj) {
792 0         0 $maskobj->mask($s);
793             }
794 0         0 $s;
795             }
796              
797             sub cache_file {
798 8     8 0 27 my $file = sprintf("%s.xlate-%s-%s.json",
799             $current_file, $xlate_engine, $lang_to);
800 8 100       34 if ($cache_method eq 'auto') {
801 5 50       202 -f $file ? $file : undef;
802             } else {
803 3 50 33     131 if ($cache_method and -f $current_file) {
804 0         0 $file;
805             } else {
806 3         67 undef;
807             }
808             }
809             }
810              
811             sub begin {
812 8 50   8 0 220 setup if not (state $done++);
813 8         39 my %args = @_;
814 8 50       58 $current_file = delete $args{&::FILELABEL} or die;
815 8 50       78 s/\z/\n/ if /.\z/;
816 8 50       32 if (not defined $xlate_engine) {
817 0         0 die "Select translation engine.\n";
818             }
819 8 50       70 if ($output_format =~ /^(:+)$/) {
820 0         0 $colon_count = length($1);
821 0         0 $output_format = 'colon';
822             }
823 8 50       33 if (my $file = cache_file) {
824 0           my @opt;
825 0 0         if ($cache_method =~ /create|clear/i) {
826 0           push @opt, clear => 1;
827             }
828 0 0         if ($cache_method =~ /accumulate/i) {
829 0           push @opt, accumulate => 1;
830             }
831 0 0         if ($force_update) {
832 0           push @opt, force_update => 1;
833             }
834 0           require App::Greple::xlate::Cache;
835 0           tie %cache, 'App::Greple::xlate::Cache', $file, @opt;
836 0 0         die "skip $current_file" if $cache_method eq 'create';
837             }
838             }
839              
840       8 0   sub end {
841             # if (my $obj = tied %cache) {
842             # $obj->update;
843             # }
844             }
845              
846             sub set {
847 0     0 0   while (my($key, $val) = splice @_, 0, 2) {
848 0 0         next if $key eq &::FILELABEL;
849 0 0         die "$key: Invalid option.\n" if not exists $opt{$key};
850 0           opt($key) = $val;
851             }
852             }
853              
854             1;
855              
856             __DATA__