File Coverage

lib/Locale/MaybeMaketext.pm
Criterion Covered Total %
statement 98 98 100.0
branch 22 22 100.0
condition n/a
subroutine 19 19 100.0
pod 7 7 100.0
total 146 146 100.0


line stmt bran cond sub pod time code
1             package Locale::MaybeMaketext;
2              
3 12     12   4423804 use v5.20.0; # minimum of v5.20.0 due to use of signatures.
  12         49  
4 12     12   72 use strict;
  12         29  
  12         353  
5 12     12   55 use warnings;
  12         38  
  12         719  
6 12     12   105 use vars;
  12         40  
  12         532  
7 12     12   64 use utf8;
  12         37  
  12         79  
8              
9 12     12   7442 use autodie qw/:all/;
  12         226144  
  12         61  
10 12     12   319980 use feature qw/signatures/;
  12         44  
  12         2121  
11 12     12   102 use Carp qw/croak/;
  12         36  
  12         808  
12 12     12   72 use Scalar::Util qw/blessed/;
  12         26  
  12         835  
13              
14             use constant {
15 12         1476 _MAYBE_MAKETEXT_ALREADY_LOADED => 1, # Localizer is already loaded.
16             _MAYBE_MAKETEXT_LOADABLE => -1, # Lcalizer is not currently loaded, but could be loaded.
17             _MAYBE_MAKETEXT_NOT_LOADABLE => 0, # Localizer is not able to be loaded.
18 12     12   88 };
  12         25  
19              
20             # indirect references (such as new Class instead of Class->new)
21             # are discouraged. can only be disabled on v5.32.0 onwards and is disabled by default on v5.36.0+.
22             # https://metacpan.org/dist/perl/view/pod/perlobj.pod#Indirect-Object-Syntax
23             # need to use the old decimal version + (patch level / 1000) version strings here
24 12     12   76 no if $] >= 5.032, q|feature|, qw/indirect/;
  12         21  
  12         1036  
25 12     12   65 no warnings qw/experimental::signatures/;
  12         30  
  12         29751  
26              
27             # ISA is needed to allow us to pick our parent
28             our @ISA = (); ## no critic (ClassHierarchies::ProhibitExplicitISA)
29              
30             our $VERSION = '1.233180'; # VERSION: inserted by Dist::Zilla::Plugin::OurPkgVersion
31              
32             # Encoding is needed for consistency with Maketext libraries
33             our $Encoding = 'utf-8'; ## no critic (NamingConventions::Capitalization,Variables::ProhibitPackageVars)
34              
35             # Which localizer package are we currently using?
36             my $maybe_maketext_has_localizer = undef;
37              
38             # What is the reasoning behind selecting that localizer?
39             my ( @maybe_maketext_default_localizers, @maybe_maketext_reasoning );
40              
41             # our default localizers;
42             @maybe_maketext_reasoning = @maybe_maketext_default_localizers = (
43             'Cpanel::CPAN::Locale::Maketext::Utils',
44             'Locale::Maketext::Utils',
45             'Locale::Maketext',
46             );
47              
48             # List of our known localizers to iterate through.
49             my @maybe_maketext_known_localizers = @maybe_maketext_default_localizers;
50              
51             ## Private functions
52              
53             # Checks to see if a package is already loaded or is loadable.
54             my $maybe_maketext_check_package_loaded = sub ($package_name) {
55             my $path = ( $package_name =~ tr{:}{\/}rs ) . '.pm';
56             if ( exists( $INC{$path} ) ) {
57             if ( defined( $INC{$path} ) ) {
58             push @maybe_maketext_reasoning, sprintf(
59             '%s: Already %s', $package_name,
60             ref( $INC{$path} )
61             ? 'loaded by hook'
62             : sprintf( 'loaded by filesystem from "%s"', $INC{$path} )
63             );
64             return _MAYBE_MAKETEXT_ALREADY_LOADED;
65             }
66              
67             # if the INC entry exists but is 'undef', that means Perl was unable
68             # to previously load the package for some unknown reason.
69             push @maybe_maketext_reasoning, sprintf(
70             '%s: Unable to set as parent localizer due to previous erroring on load',
71             $package_name,
72             );
73             return _MAYBE_MAKETEXT_NOT_LOADABLE;
74             }
75             push @maybe_maketext_reasoning, sprintf(
76             '%s: No record of load attempt found',
77             $package_name,
78             );
79             return _MAYBE_MAKETEXT_LOADABLE;
80              
81             };
82              
83             # Try and load a specific localization library.
84             # Will attempt to try to load if if already loaded - use maybe_maketext_check_package_loaded to check!
85             # Returns 1 if correctly loaded, 0 if not loaded (for whatever reason)
86             my $maybe_maketext_try_load = sub ($package_name) {
87             my $path = ( $package_name =~ tr{:}{\/}rs ) . '.pm';
88             my $loaded = 0;
89             if (
90             !eval {
91              
92             # Convert any warnings encountered during loading
93             # into "dies" to catch "Subroutine redefined at..." and similar messages.
94             local $SIG{__WARN__} = sub { die $_[0] }; ## no critic (ErrorHandling::RequireCarping)
95             # try the load.
96             require $path;
97             if ( defined( $INC{$path} ) ) {
98              
99             # ensure it has loaded - and if so, record what loaded.
100             push @maybe_maketext_reasoning, sprintf(
101             '%s: Loaded correctly from %s',
102             $package_name,
103             ref( $INC{$path} )
104             ? 'loaded by hook'
105             : sprintf( 'loaded by filesystem from "%s"', $INC{$path} )
106             );
107             $loaded = 1;
108             }
109             else {
110             push @maybe_maketext_reasoning, sprintf(
111             '%s: Failed to correctly load from %s',
112             $package_name,
113             $path
114             );
115             }
116             1;
117             }
118             ) {
119             # reached if any part of the previous code block errors (such as a loading issue).
120             push @maybe_maketext_reasoning, sprintf(
121             '%s: Unable to set as parent localizer due to "%s" when loading from %s',
122             $package_name,
123             $@ =~ tr{\n}{ }rs,
124             $path
125             );
126             }
127             return $loaded;
128             };
129              
130             # Check we are passed in an appropriate class object when needed.
131             my $maybe_maketext_check_passed_class = sub ( $class, $method ) {
132             if ( !defined($class) ) {
133             croak( sprintf( '%s should not be called without a class', $method ) );
134             }
135             my $is_blessed = blessed($class);
136             if ( defined($is_blessed) ) {
137              
138             # we have an object, let's store its name as a scalar.
139             $class = $is_blessed;
140             }
141             elsif ( ref($class) ne q{} ) {
142              
143             # it is a reference to something else.
144             croak(
145             sprintf( '%s should only be called with class objects: provided a reference of %s', $method, ref($class) )
146             );
147             }
148              
149             # check to see if it is ourselves being called.
150             if ( $class eq __PACKAGE__ ) {
151             croak( sprintf( '%s should be called on the translation file\'s parent class', $method ) );
152             }
153              
154             # we should have a scalar now.
155             if ( !$class->can($method) ) {
156             croak( sprintf( '%s was provided as a class to %s but it does not support %s', $class, $method, $method ) );
157             }
158             return 1;
159             };
160              
161             ## Public functions
162              
163             # Gets the list of known localizers.
164 4     4 1 42 sub maybe_maketext_get_localizer_list() {
  4         7  
165 4         18 return @maybe_maketext_known_localizers;
166             }
167              
168             # Sets the list of known localizers.
169 3     3 1 1664 sub maybe_maketext_set_localizer_list (@localizers) {
  3         12  
  3         7  
170 3         11 @maybe_maketext_known_localizers = @localizers;
171 3         13 return 1;
172             }
173              
174             # Gets the current localizer if set - if not, tries to load an appropriate one
175             # using the private 'maybe_maketext_try_load' method.
176 25     25 1 3976 sub maybe_maketext_get_localizer() {
  25         45  
177 25 100       85 if ($maybe_maketext_has_localizer) {
178              
179             # already exists
180 12         41 return $maybe_maketext_has_localizer;
181             }
182 13         35 my @to_attempt = ();
183 13         39 for my $package_name (@maybe_maketext_known_localizers) {
184 32         104 my $is_loaded = $maybe_maketext_check_package_loaded->($package_name);
185 32 100       116 if ( $is_loaded == _MAYBE_MAKETEXT_ALREADY_LOADED ) {
    100          
186 3         7 $maybe_maketext_has_localizer = $package_name;
187 3         7 last;
188             }
189             elsif ( $is_loaded == _MAYBE_MAKETEXT_LOADABLE ) {
190 28         132 push @to_attempt, $package_name;
191             }
192             }
193 13 100       50 if ( !$maybe_maketext_has_localizer ) {
194 10         24 push @maybe_maketext_reasoning, 'Attempting to load';
195 10         27 for my $package_name (@to_attempt) {
196 18 100       57 if ( $maybe_maketext_try_load->($package_name) ) {
197 7         34 $maybe_maketext_has_localizer = $package_name;
198 7         36 last;
199             }
200             }
201             }
202 13 100       84 if ( !$maybe_maketext_has_localizer ) {
203 3         718 croak( "Unable to load localizers: \n - " . join( "\n - ", @maybe_maketext_reasoning ) );
204             }
205              
206             # Needed to allow us to pick our parent
207 10         320 push @ISA, $maybe_maketext_has_localizer; ## no critic (ClassHierarchies::ProhibitExplicitISA)
208 10         86 return $maybe_maketext_has_localizer;
209             }
210              
211             # Reset which localizer we are currently using.
212 15     15 1 2839015 sub maybe_maketext_reset () {
  15         40  
213              
214             # remove parent/inheritance.
215 15 100       69 if ($maybe_maketext_has_localizer) {
216             ## no critic (ClassHierarchies::ProhibitExplicitISA)
217             # If we inherited from other classes, it would be advisable to
218             # only remove the localizer - using this grep command:
219             #@ISA = grep { !/$maybe_maketext_has_localizer/ } @ISA;
220             # but since we don't, we can just reset the inheritence.
221 4         132 @ISA = ();
222 4         28 $maybe_maketext_has_localizer = undef;
223             }
224 15         79 @maybe_maketext_known_localizers = @maybe_maketext_default_localizers;
225 15         46 @maybe_maketext_reasoning = ();
226 15         41 return 1;
227             }
228              
229             # Get the reasoning for the current localizer.
230 5     5 1 5104 sub maybe_maketext_get_reasoning() {
  5         9  
231 5         22 return @maybe_maketext_reasoning;
232             }
233              
234             # Get the localizer translation handle (after ensuring we have an appropriate
235             # localizer of course).
236 20     20 1 52318 sub get_handle ( $class, @languages ) {
  20         48  
  20         44  
  20         33  
237 20         80 maybe_maketext_get_localizer(); # don't actually care about which localizer we get
238 18         67 $maybe_maketext_check_passed_class->( $class, 'get_handle' );
239              
240 14         101 my $return = $class->SUPER::get_handle(@languages);
241 14         12244 return $return;
242             }
243              
244             # Dummy method to ensure a localizer is set through get_handle first.
245 60     60 1 65694 sub maketext ( $class, $string, @params ) {
  60         131  
  60         120  
  60         129  
  60         95  
246 60 100       206 if ( !$maybe_maketext_has_localizer ) {
247 1         131 croak('maketext called without get_handle');
248             }
249 59 100       155 if ( !ref($class) ) {
250 1         224 croak('maketext must be called as a method');
251             }
252 58         189 $maybe_maketext_check_passed_class->( $class, 'maketext' );
253 55 100       147 if ( !defined($string) ) {
254 1         129 croak('maketext must be passed a scalar string to translate - it was passed an undefined item');
255             }
256 54 100       145 if ( ref($string) ne q{} ) {
257 1         131 croak(
258             sprintf(
259             'maketext must be passed a scalar string to translate - it was passed a %s reference', ref($string)
260             )
261             );
262             }
263              
264 53         322 return $class->SUPER::maketext( $string, @params );
265             }
266              
267             1;
268              
269             __END__
270              
271             =encoding utf8
272              
273             =head1 NAME
274              
275             Locale::MaybeMaketext - Find available localization / localisation / translation services.
276              
277             =head1 VERSION
278              
279             version 1.233180
280              
281             =head1 DESCRIPTION
282              
283             There are, to my knowledge, three slightly different Maketext libraries available on Perl
284             all of which require your "translation files" to reference that individual library as a
285             parent/base package: which causes problems if you want to support all three. This package
286             addresses this issue by allowing you to just reference this package and then it will automatically
287             figure out which Maketext library is available on the end-users platform.
288              
289             It will try each localizer in the order:
290              
291             * L<Cpanel::CPAN::Locale::Maketext::Utils>
292              
293             * L<Locale::Maketext::Utils>
294              
295             * L<Locale::Maketext>
296              
297             =head1 SYNOPSIS
298              
299             How to use:
300              
301             1. Create a base/parent localization class which uses C<Locale::MaybeMaketext> as the parent:
302              
303             # File YourProjClass/L10N.pm
304             package YourProjClass::L10N;
305             use parent qw/Locale::MaybeMaketext/;
306             # any additional methods to share on all languages
307             1;
308              
309             2. Create the individual translation files using your base/parent class as the parent:
310              
311             # File YourProjClass/L10N/en.pm
312             package YourProjClass::L10N::EN;
313             use parent qw/YourProjClass::L10N/;
314             %Lexicon = (
315             '_AUTO'=>1,
316             );
317             1;
318              
319             3. In your main program use:
320              
321             # File YourProjClass/Main.pl
322             use parent qw/YourProjClass::L10N/;
323             ...
324             my $lh=YourProjClass::L10N->get_handle() || die('Unable to find language');
325             print $lh->maketext("Hello [_1] thing\n",$thing);
326              
327             =head1 METHODS
328              
329             The main method you need to concern yourself about is the C<get_handle> method
330             which gets an appropriate localizer, sets it as the "parent" of the package
331             and then returns an appropriate C<maketext> handle.
332              
333             =over
334              
335             =item $lh = YourProjClass->get_handle(...langtags...) || die 'language handle?';
336              
337             This ensures an appropriate localizer/Maketext library is set as the parent
338             and then tries loading classes based on the language-tags (langtags) you provide -
339             and for the first class that succeeds, returns YourProjClass::I<language>->new().
340              
341             =item $lh = YourProjClass->get_handle() || die 'language handle?';
342              
343             This ensures an appropriate localizer/Maketext library is set as the parent
344             and then asks that library to "magically" detect the most appropriate language
345             for the user based on its own logic.
346              
347             =item $localizer = Locale::MaybeMaketext::maybe_maketext_get_localizer();
348              
349             Returns the package name of the currently selected localizer/Maketext library -
350             or, if one is not set, will try and pick one from the list in
351             C<@maybe_maketext_known_localizers> and return that. If it is unable to find
352             a localizer (for example, if the user has none of the listed packages installed),
353             then the C<croak> error message "Unable to load localizers: "... will be emitted
354             along with why/how it was unable to load each localizer.
355              
356             =item Locale::MaybeMaketext::maybe_maketext_reset();
357              
358             Removes the currently set localizer from the package. Intended for testing purposes.
359              
360             =item $text = $lh->maketext(I<key>, ... parameters for this phrase ... );
361              
362             This is actually just a dummy function to ensure that C<get_handle> is called
363             before any attempt is made to translate text.
364              
365             =item @list = Locale::MaybeMaketext::maybe_maketext_get_localizer_list();
366              
367             Get the list of currently configured localizers. Intended for testing purposes.
368              
369             =item Locale::MaybeMaketext::maybe_maketext_set_localizer_list(@<list of localizers>);
370              
371             Sets the list of currently configured localizers. Intended for testing purposes.
372              
373             =item @reason = Locale::MaybeMaketext::maybe_maketext_get_reasoning()
374              
375             Returns the reasoning "why" a particular localizer was choise. Intended for debugging purposes.
376              
377             =back
378              
379             =head2 Utility Methods
380              
381             Various C<Maketext> libraries support different 'utility modules' which help
382             expand the bracket notation used in Maketext. Of course, you do not necessarily
383             know which localization library will be used so it is advisable to keep to the
384             most commonly supported utility methods.
385              
386             Here is a little list of which utility modules are available under which library:
387              
388             * LM = Locale::Maketext
389              
390             * LMU = Locale::Maketext::Utils
391              
392             * CCLMU = Cpanel::CPAN::Locale::Maketext::Utils
393              
394             |-------------------------------------------|
395             | Method | LM | LMU | CCLMU |
396             |-------------------|-------|-------|-------|
397             | quant | Y | Y | Y |
398             | numf | Y | Y | Y |
399             | numerate | Y | Y | Y |
400             | sprintf | Y | Y | Y |
401             | language_tag | Y | Y | Y |
402             | encoding | Y | Y | Y |
403             | join | N | Y | Y |
404             | list_and | N | Y | Y |
405             | list_or | N | Y | Y |
406             | list_and_quoted | N | Y | Y |
407             | list_or_quoted | N | Y | Y |
408             | datetime | N | Y | Y |
409             | current_year | N | Y | Y |
410             | format_bytes | N | Y | Y |
411             | convert | N | Y | Y |
412             | boolean | N | Y | Y |
413             | is_defined | N | Y | Y |
414             | is_future | N | Y | Y |
415             | comment | N | Y | Y |
416             | asis | N | Y | Y |
417             | output | N | Y | Y |
418             |-------------------------------------------|
419              
420             =head1 AUTHORS
421              
422             =over 4
423              
424             =item Richard Bairwell E<lt>rbairwell@cpan.orgE<gt>
425              
426             =back
427              
428             =head1 COPYRIGHT
429              
430             Copyright 2023 Richard Bairwell E<lt>rbairwell@cpan.orgE<gt>
431              
432             This program is free software; you can redistribute it and/or
433             modify it under the same terms as Perl itself. The full text
434             of this license can be found in the F<LICENSE> file
435             included with this module.
436              
437             See F<http://dev.perl.org/licenses/>
438              
439             =cut