File Coverage

blib/lib/PGXN/Site/Locale.pm
Criterion Covered Total %
statement 66 67 98.5
branch 15 18 83.3
condition 1 3 33.3
subroutine 13 13 100.0
pod 4 4 100.0
total 99 105 94.2


line stmt bran cond sub pod time code
1             package PGXN::Site::Locale;
2              
3 1     1   77861 use 5.10.0;
  1         15  
4 1     1   7 use utf8;
  1         3  
  1         6  
5 1     1   595 use parent 'Locale::Maketext';
  1         352  
  1         6  
6 1     1   15078 use I18N::LangTags::Detect;
  1         3  
  1         30  
7 1     1   7 use File::Spec;
  1         2  
  1         22  
8 1     1   6 use Carp;
  1         3  
  1         1028  
9             our $VERSION = v0.22.1;
10              
11             # Allow unknown phrases to just pass-through.
12             our %Lexicon = (
13             # _AUTO => 1,
14             listcomma => ',',
15             listand => 'and',
16             openquote => '“',
17             shutquote => '”',
18             in => 'in',
19             hometitle => 'PGXN: PostgreSQL Extension Network',
20             'PostgreSQL Extension Network' => 'PostgreSQL Extension Network',
21             'PGXN Gear' => 'PGXN Gear',
22             'Recent' => 'Recent',
23             'Recent Releases' => 'Recent Releases',
24             'About' => 'About',
25             'About PGXN' => 'About PGXN',
26             'PGXN Users' => 'PGXN Users',
27             'Recent' => 'Recent',
28             'User' => 'User',
29             'Users' => 'Users',
30             'Recent' => 'Recent',
31             'Recent Releases' => 'Recent Releases',
32             'Blog' => 'Blog',
33             'PGXN Blog' => 'PGXN Blog',
34             'FAQ' => 'FAQ',
35             'Frequently Asked Questions' => 'Frequently Asked Questions',
36             'Release It' => 'Release It',
37             'Release it on PGXN' => 'Release it on PGXN',
38             code => 'code',
39             design => 'design',
40             logo => 'logo',
41             'Go to [_1]' => 'Go to [_1]',
42             Mirroring => 'Mirroring',
43             'Mirroring PGXN' => 'Mirroring PGXN',
44             Feedback => 'Feedback',
45             Identity => 'Identity',
46             Extensions => 'Extensions',
47             Tags => 'Tags',
48             'Release Tags' => 'Release Tags',
49             Distributions => 'Distributions',
50             'PGXN Search' => 'PGXN Search',
51             pgxn_summary_paragraph => 'PGXN, the PostgreSQL Extension network, is a central distribution system for open-source PostgreSQL extension libraries.',
52             Founders => 'Founders',
53             Patrons => 'Patrons',
54             Benefactors => 'Benefactors',
55             Sponsors => 'Sponsors',
56             Advocates => 'Advocates',
57             Supporters => 'Supporters',
58             Boosters => 'Boosters',
59             'Donors' => 'Donors',
60             'See a longer list of recent releases.' => 'See a longer list of recent releases.',
61             'More Releases' => 'More Releases →',
62             'Not Found' => 'Not Found',
63             'Resource not found.' => 'Resource not found.',
64             'Resource Not Found' => 'Resource Not Found',
65             'Internal Server Error' => 'Internal Server Error',
66             'Internal server error.' => 'Internal server error.',
67             'Download' => 'Download',
68             'Download [_1] [_2]' => 'Download [_1] [_2]',
69             'Browse [_1] [_2]' => 'Browse [_1] [_2]',
70             'Alas, [_1] has yet to release a distribution.' => 'Alas, [_1] has yet to release a distribution.',
71             'This Release' => 'This Release',
72             'Date' => 'Date',
73             'Latest Stable' => 'Latest Stable',
74             'Latest Testing' => 'Latest Testing',
75             'Latest Unstable' => 'Latest Unstable',
76             'Other Releases' => 'Other Releases',
77             'Status' => 'Status',
78             'stable' => 'Stable',
79             'testing' => 'Testing',
80             'unstable' => 'Unstable',
81             'Abstract' => 'Abstract',
82             'Description' => 'Description',
83             'Maintainer' => 'Maintainer',
84             'Maintainers' => 'Maintainers',
85             'License' => 'License',
86             'Resources' => 'Resources',
87             'www' => 'www',
88             'bugs' => 'bugs',
89             'repo' => 'repo',
90             'Special Files' => 'Special Files',
91             'Tags' => 'Tags',
92             'Other Documentation' => 'Other Documentation',
93             'Released By' => 'Released By',
94             'README' => 'README',
95             'Documentation' => 'Documentation',
96             'Nickname' => 'Nickname',
97             'URL' => 'URL',
98             'Email' => 'Email',
99             'Twitter' => 'Twitter',
100             'Follow PGXN on Twitter' => 'Follow PGXN on Twitter',
101             'Browse' => 'Browse',
102             'Tag: [_1]' => 'Tag: “[_1]”',
103             'PGXN Search' => 'PGXN Search',
104             'In the [_1] distribution' => 'In the [_1] distribution',
105             'Released by [_1]' => 'Released by [_1]',
106             'Search matched no documents.' => 'Search matched no documents.',
107             'Previous results' => 'Previous results',
108             'Next results' => 'Next results',
109             '← Prev' => '← Prev',
110             'Next →' => 'Next →',
111             '[_1]-[_2] of [_3] found' => '[_1]-[_2] of [_3] found',
112             'No Releases Yet' => 'No Releases Yet',
113             'PGXN Meta Spec' => 'PGXN Meta Spec',
114             'Bad Request' => 'Bad Request',
115             'Bad request: Missing or invalid "[_1]" query parameter.' => 'Bad request: Missing or invalid “[_1]” query parameter.',
116             'Search Users' => 'Search Users',
117             'Or select a letter' => 'Or select a letter',
118             'Nicknames starting with "[_1]"' => 'User nicknames starting with “[_1]”',
119             'None found' => 'None found',
120             'Search all indexed extensions, distributions, users, and tags on the PostgreSQL Extension Network.' => 'Search all indexed extensions, distributions, users, and tags on the PostgreSQL Extension Network.',
121             'No user nicknames found starting with "[_1]"' => 'No user nicknames found starting with “[_1]”',
122             'Contact and extension release information for PGXN user "[_1]"' => 'Contact and extension release information for PGXN user “[_1]”',
123             'Search for tags on PostgreSQL extension releases on PGXN' => 'Search for tags on PostgreSQL extension releases on PGXN',
124             'Search for PostgreSQL Extension Network users' => 'Search for PostgreSQL Extension Network users',
125             'A list of PGXN extensions tagged "[_1]"' => 'A list of PGXN extensions tagged “[_1]”',
126             'PGXN [_1] search results for "[_2]"' => 'PGXN [_1] search results for “[_2]”',
127             'Submit feedback to PGXN or join the mail list' => 'Submit feedback to PGXN or join the mail list',
128             'Background on PGXN' => 'All about PGXN, what it’s for, what it contains, who made it, and why',
129             'donor description' => 'Many thanks to these fine organizations and people who contributed support to make the develpoment of PGXN possible',
130             'identity description' => 'All about the PGXN identity: who created it, its license, and downloadable assets',
131             'faq description' => 'Frequently asked questions about the PostgreSQL Extension Network',
132             'mirroring description' => 'Step-by-step instructions for mirroring PGXN on your own server',
133             'Recent PostgreSQL extension releases on PGXN' => 'Recent PostgreSQL extension releases on PGXN',
134             donors_intro => 'All the great folks who funded the inital development of PGXN will be listed in perpetuity here on the “Donors” page of PGXN.org. All donors are invited to the PGXN Launch Party at PGCon in May, 2011.',
135             );
136              
137             sub accept {
138 2     2 1 1504 shift->get_handle( I18N::LangTags::Detect->http_accept_langs(shift) );
139             }
140              
141             sub list {
142 6     6 1 3630 my ($lh, $items) = @_;
143 6 50       12 return unless @{ $items };
  6         20  
144 6 100       12 return $items->[0] if @{ $items } == 1;
  6         23  
145 4         9 my $last = pop @{ $items };
  4         9  
146 4         18 my $comma = $lh->maketext('listcomma');
147 4         143 my $ret = join "$comma ", @$items;
148 4 100       8 $ret .= $comma if @{ $items } > 1;
  4         26  
149 4         14 my $and = $lh->maketext('listand');
150 4         136 return "$ret $and $last";
151             }
152              
153             sub qlist {
154 6     6 1 5285 my ($lh, $items) = @_;
155 6 50       10 return unless @{ $items };
  6         20  
156 6         20 my $open = $lh->maketext('openquote');
157 6         194 my $shut = $lh->maketext('shutquote');
158 6 100       158 return $open . $items->[0] . $shut if @{ $items } == 1;
  6         31  
159 4         7 my $last = pop @{ $items };
  4         10  
160 4         10 my $comma = $lh->maketext('listcomma');
161 4         115 my $ret = $open . join("$shut$comma $open", @$items) . $shut;
162 4 100       8 $ret .= $comma if @{ $items } > 1;
  4         12  
163 4         14 my $and = $lh->maketext('listand');
164 4         123 return "$ret $and $open$last$shut";
165             }
166              
167             my %PATHS_FOR;
168              
169             sub DESTROY {
170 9     9   7037 delete $PATHS_FOR{ ref shift };
171             }
172              
173             sub from_file {
174 3     3 1 11 my ($self, $path) = (shift, shift);
175 3         7 my $class = ref $self;
176 3   33     23 my $file = $PATHS_FOR{$class}{$path} ||= _find_file($class, $path);
177 3 50       188 open my $fh, '<:utf8', $file or die "Cannot open $file: $!\n";
178 3         9 my $value = do { local $/; $self->_compile(<$fh>); };
  3         16  
  3         193  
179 3 100       1497 return ref $value eq 'CODE' ? $value->($self, @_) : ${ $value };
  2         68  
180             }
181              
182             sub _find_file {
183 3     3   6 my $class = shift;
184 3         11 my @path = split m{/}, shift;
185 3         21 (my $dir = __FILE__) =~ s{[.]pm$}{};
186 1     1   11 no strict 'refs';
  1         9  
  1         196  
187 3         7 foreach my $super ($class, @{$class . '::ISA'}, __PACKAGE__ . '::en') {
  3         23  
188 7         52 my $file = File::Spec->catfile($dir, $super->language_tag, @path);
189 7 100       435 return $file if -e $file;
190             }
191 0           croak "No file found for path " . join('/', @path);
192             }
193              
194             1;
195              
196             =encoding utf8
197              
198             =head1 Name
199              
200             PGXN::Site::Locale - Localization for PGXN::Site
201              
202             =head1 Synopsis
203              
204             use PGXN::Site::Locale;
205             my $mt = PGXN::Site::Locale->accept($env->{HTTP_ACCEPT_LANGUAGE});
206              
207             =head1 Description
208              
209             This class provides localization support for PGXN::Site. Each locale must
210             create a subclass named for the locale and put its translations in the
211             C<%Lexicon> hash. It is further designed to support easy creation of
212             a handle from an HTTP_ACCEPT_LANGUAGE header.
213              
214             =head1 Interface
215              
216             The interface inherits from L and adds the following
217             method.
218              
219             =head2 Constructor Methods
220              
221             =head3 C
222              
223             my $mt = PGXN::Site::Locale->accept($env->{HTTP_ACCEPT_LANGUAGE});
224              
225             Returns a PGXN::Site::Locale handle appropriate for the specified
226             argument, which must take the form of the HTTP_ACCEPT_LANGUAGE string
227             typically created in web server environments and specified in L
228             3282|https://tools.ietf.org/html/rfc3282>. The parsing of this header is
229             handled by L.
230              
231             =head2 Instance Methods
232              
233             =head3 C
234              
235             # "Missing these keys: foo, bar, and baz"
236             say $mt->maketext(
237             'Missing these keys: [list,_1])'
238             [qw(foo bar baz)],
239             );
240              
241             Formats a list of items. The list of items to be formatted should be passed as
242             an array reference. If there is only one item, it will be returned. If there
243             are two, they will be joined with " and ". If there are more, there will be a
244             comma-separated list with the final item joined on ", and ".
245              
246             Note that locales can control the localization of the comma and "and" via the
247             C and C entries in their C<%Lexicon>s.
248              
249             =head3 C
250              
251             # "Missing these keys: “foo”, “bar”, and “baz”
252             say $mt->maketext(
253             'Missing these keys: [qlist,_1]'
254             [qw(foo bar baz)],
255             );
256              
257             Like C but quotes each item in the list. Locales can specify the
258             quotation characters to be used via the C and C entries
259             in their C<%Lexicon>s.
260              
261             =head3 C
262              
263             my $text = $mt->from_file('foo/bar.html');
264             my $msg = $mt->from_file('feedback.html', 'pgxn@example.com');
265              
266             Returns the contents of a localized file. The file argument should be
267             specified with Unix semantics, regardless of operating system. Whereas
268             subclasses contain short strings that need translating, the files can contain
269             complete documents. As with C, the support the full range variable
270             substitution, such as C<[_1]> and friends.
271              
272             If a file doesn't exist for the current language, C will fall
273             back on the same file path for any of its parent classes. If none has the
274             file, it will fall back on the English file.
275              
276             Localized files are maintained in L format by translators
277             and converted to HTML at build time. The live in a subdirectory named for the
278             last part of a subclass's package name. For example, the
279             L class lives in F. Localized
280             files will live in F. So for the argument
281             C, the localized file will be
282             F, and the HTML file (created at build time)
283             will be F.
284              
285             =head1 Author
286              
287             David E. Wheeler
288              
289             =head1 Copyright and License
290              
291             Copyright (c) 2010-2021 David E. Wheeler.
292              
293             This module is free software; you can redistribute it and/or modify it under
294             the L.
295              
296             Permission to use, copy, modify, and distribute this software and its
297             documentation for any purpose, without fee, and without a written agreement is
298             hereby granted, provided that the above copyright notice and this paragraph
299             and the following two paragraphs appear in all copies.
300              
301             In no event shall David E. Wheeler be liable to any party for direct,
302             indirect, special, incidental, or consequential damages, including lost
303             profits, arising out of the use of this software and its documentation, even
304             if David E. Wheeler has been advised of the possibility of such damage.
305              
306             David E. Wheeler specifically disclaims any warranties, including, but not
307             limited to, the implied warranties of merchantability and fitness for a
308             particular purpose. The software provided hereunder is on an "as is" basis,
309             and David E. Wheeler has no obligations to provide maintenance, support,
310             updates, enhancements, or modifications.
311              
312             =cut