1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
|
<?php /** * pre-msginit function. Prepares arguments for creating a new PO language file */ class Loco_admin_init_InitPoController extends Loco_admin_bundle_BaseController { /** * {@inheritdoc} */ public function init(){ parent::init(); $this->enqueueStyle('poinit'); // $bundle = $this->getBundle(); $this->set('title', __('New language','loco-translate').' ‹ '.$bundle ); }
/** * {@inheritdoc} */ public function getHelpTabs(){ return [ __('Overview') => $this->viewSnippet('tab-init-po'), ]; }
/** * Sort to the left the best option for saving new translation files. * This favours custom locations, with system secondary. No other locations will be pre-selected. * @param Loco_mvc_ViewParams[] $choices * @return Loco_mvc_ViewParams|null */ private function sortPreferred( array $choices ){ usort( $choices, [__CLASS__,'_onSortPreferred'] ); foreach( $choices as $choice ){ if( $choice['disabled'] || $choice['copying'] || $choice['exists']){ continue; } // avoid promoting the Author location $t = $choice['dirtype']; if( 'theme' === $t || 'plugin' === $t){ continue; } // pre-select this. It should be a writable custom or system file. return $choice; } return null; }
/** * @internal * @return int */ public static function _onSortPreferred( Loco_mvc_ViewParams $a, Loco_mvc_ViewParams $b ){ $x = self::scoreFileChoice($a); $y = self::scoreFileChoice($b); return $x === $y ? 0 : ( $x > $y ? -1 : 1 ); } /** * Score an individual file choice for sorting preferred * @return int */ private static function scoreFileChoice( Loco_mvc_ViewParams $p ){ $score = 0; if( $p['writable'] ){ $score++; } if( $p['disabled'] ){ $score -= 2; } if( $p['systype'] ){ $score--; } return $score; }
/** * @internal * @param int $a * @param int $b * @return int */ private static function compareLocationKeys( $a, $b ){ static $order = ['custom' => 4, 'wplang' => 3, 'theme' => 2, 'plugin' => 2, 'other' => 1 ]; $x = $order[$a]; $y = $order[$b]; return $x === $y ? 0 : ( $x > $y ? -1 : 1 ); }
/** * {@inheritdoc} */ public function render(){ $breadcrumb = $this->prepareNavigation(); // "new" tab is confusing when no project-scope navigation // $this->get('tabs')->add( __('New PO','loco-translate'), '', true ); // bundle mandatory, but project optional $bundle = $this->getBundle();
try { $project = $this->getProject(); $slug = $project->getSlug(); $domain = (string) $project->getDomain(); // translators: %s refers to the slug/handle of a theme or plugin $subhead = sprintf( __('Initializing new translations in "%s"','loco-translate'), $slug?:$domain ); } catch( Loco_error_Exception $e ){ $project = null; $subhead = __('Initializing new translations in unknown set','loco-translate'); }
$title = __('New language','loco-translate'); $this->set('subhead', $subhead ); // navigate up to bundle listing page $breadcrumb->add( $title ); $this->set( 'breadcrumb', $breadcrumb ); // default locale is a placeholder $localeTag = ''; $locale = new Loco_Locale('zxx'); $content_dir = untrailingslashit( loco_constant('WP_CONTENT_DIR') ); $extracting = (bool) $this->get('extract'); $copying = false; $sourcedir = ''; // Permit using any provided file a template instead of POT if( $potpath = $this->get('path') ){ $potfile = new Loco_fs_LocaleFile($potpath); $potfile->normalize($content_dir); if( ! $potfile->exists() ){ throw new Loco_error_Exception('Forced template argument must exist'); } $copying = true; $sourcedir = $potfile->dirname(); // forced source could be a POT (although UI would normally prevent it) if( $potfile->getSuffix() ){ $locale = $potfile->getLocale(); if( $locale->isValid() ){ $this->set('sourceLocale', $locale ); $localeTag = (string) $locale; } } } // else project not configured. UI should prevent this by not offering msginit else if( ! $project ){ throw new Loco_error_Exception('Cannot add new language to unconfigured set'); } // else POT file may or may not be known, and may or may not exist else { $potfile = $project->getPot(); }
$locales = []; $installed = []; $api = new Loco_api_WordPressTranslations; $prefs = Loco_data_Preferences::get(); // pull installed list first, this will include en_US and any non-standard languages installed foreach( $api->getInstalledCore() as $tag ){ $tmp = Loco_Locale::parse($tag); if( $tmp->isValid() && $prefs->has_locale($tmp) ){ $tag = (string) $tmp; // We may not have names for these, so just the language tag will show $installed[$tag] = new Loco_mvc_ViewParams( [ 'value' => $tag, 'icon' => $tmp->getIcon(), 'label' => $tmp->ensureName($api), 'selected' => $tag === $localeTag ? ' selected' : '' ] ); } } // pull the same list of "available" languages as used in WordPress settings foreach( $api->getAvailableCore() as $tag => $tmp ){ if( ! array_key_exists($tag,$installed) && $prefs->has_locale($tmp) ){ $locales[$tag] = new Loco_mvc_ViewParams( [ 'value' => $tag, 'icon' => $tmp->getIcon(), 'label' => $tmp->ensureName($api), 'selected' => $tag === $localeTag ? ' selected' : '' ] ); } }
// two locale lists built for "installed" and "available" dropdowns. Else populate custom field. $this->set('locales', $locales ); $this->set('installed', $installed ); if( $localeTag && ! array_key_exists($localeTag,$installed) && ! array_key_exists($localeTag,$locales) ){ $this->set('custom', $localeTag ); }
// Critical that user selects the correct save location: if( $project ){ $filechoice = $project->initLocaleFiles( $locale ); } // without configured project we will only allow save to same location else { $filechoice = new Loco_fs_FileList; } // Define suitable prompts for extracting from source, creating a template, or (preferred) copy an existing PO. $prompts = []; if( ! $copying ){ $args = array_intersect_key( $_GET,['bundle'=>'','domain'=>'']); // link to extract a new template if none exists $prompts['xgettext'] = new Loco_mvc_ViewParams( [ 'link' => Loco_mvc_AdminRouter::generate( $this->get('type' ).'-xgettext', $args ), 'text' => __('Create template','loco-translate'), ] ); // link to direct extraction here, if not already doing $prompts['skip'] = new Loco_mvc_ViewParams( [ 'link' => Loco_mvc_AdminRouter::generate( $this->get('_route'), $args + ['extract'=>'1'] ), 'text' => __('Skip template','loco-translate'), ] ); // link to copy an existing PO file, if there's an installed one /* @var Loco_fs_LocaleFile $pofile */ foreach( $project->findLocaleFiles('po') as $pofile ){ $dir = new Loco_fs_LocaleDirectory( $pofile->dirname() ); if( 'wplang' !== $dir->getTypeId() ){ continue; } // we don't know yet what locale the user will select, so pick first PO. $args = array_intersect_key( $_GET,['bundle'=>'','domain'=>'']); $args['path'] = $pofile->getRelativePath($content_dir); $prompts['copy'] = new Loco_mvc_FileParams( [ 'link' => Loco_mvc_AdminRouter::generate( $this->get('type' ).'-msginit', $args ), 'text' => __('Copy PO file','loco-translate'), ], $pofile ); break; } }
// show information about POT file if we are initializing from template if( $potfile && $potfile->exists() ){ $meta = Loco_gettext_Metadata::load($potfile); $total = $meta->getTotal(); // translators: 1: Number of strings; 2: Name of POT file; e.g. "100 strings found in file.pot" $summary = sprintf( _n('%1$s string found in %2$s','%1$s strings found in %2$s',$total,'loco-translate'), number_format($total), $potfile->basename() ); $this->set( 'pot', new Loco_mvc_ViewParams( [ 'name' => $potfile->basename(), 'path' => $meta->getPath(false), ] ) ); // if copying an existing PO file, we can fairly safely establish the correct prefixing if( $copying ){ $prefix = $potfile->getPrefix(); $poname = $prefix ? sprintf('%s-%s.po',$prefix,$locale) : sprintf('%s.po',$locale); $pofile = new Loco_fs_LocaleFile( $poname ); $pofile->normalize( $potfile->dirname() ); $filechoice->add( $pofile ); } } // else not initializing from template- prompt to copy existing PO file else if( array_key_exists('copy',$prompts) && ! $extracting ){ $this->set('copy', $prompts['copy'] ); $this->set('skip', $prompts['skip'] ); $this->set('ext', $prompts['xgettext'] ); return $this->view('admin/init/init-copy'); } // else prompt to extract from source (advanced). else if( 2 > Loco_data_Settings::get()->pot_expected ){ // if allowing source extraction without warning show brief description of source files if( $extracting || 0 === Loco_data_Settings::get()->pot_expected ){ // Tokenizer required for string extraction if( ! loco_check_extension('tokenizer') ){ return $this->view('admin/errors/no-tokenizer'); } $nfiles = count( $project->findSourceFiles() ); // translators: Were %s is number of source files that will be scanned $summary = sprintf( _n('%s source file will be scanned for translatable strings','%s source files will be scanned for translatable strings',$nfiles,'loco-translate'), number_format_i18n($nfiles) ); $extracting = true; } // else prompt for template creation before continuing else { // POT could still be defined, it might just not exist yet if( $potfile ){ $this->set('pot', Loco_mvc_FileParams::create($potfile) ); } // else offer assignment of a new file else { $this->set( 'conf', new Loco_mvc_ViewParams( [ 'link' => Loco_mvc_AdminRouter::generate( $this->get('type').'-conf', array_intersect_key($_GET,['bundle'=>'']) ), 'text' => __('Assign template','loco-translate'), ] ) ); } $this->set('ext', $prompts['xgettext'] ); $this->set('skip', $prompts['skip'] ); return $this->view('admin/init/init-prompt'); } } else { throw new Loco_error_Exception('Plugin settings disallow missing templates'); } $this->set( 'summary', $summary ); // group established locations into types (official, etc..) // there is no point checking whether any of these file exist, because we don't know what language will be chosen yet. $sortable = []; $locations = []; $fs_failure = null; /* @var Loco_fs_LocaleFile $pofile */ foreach( $filechoice as $pofile ){ $parent = new Loco_fs_LocaleDirectory( $pofile->dirname() ); $systype = $parent->getUpdateType(); $typeId = $parent->getTypeId(); if( ! isset($locations[$typeId]) ){ $locations[$typeId] = new Loco_mvc_ViewParams( [ 'label' => $parent->getTypeLabel( $typeId ), 'paths' => [], ] ); } // folder may be unwritable (requiring connect to create file) or may be denied under security settings try { $context = $parent->getWriteContext()->authorize(); $writable = $context->writable(); $disabled = false; } catch( Loco_error_WriteException $e ){ $fs_failure = $e->getMessage(); $writable = false; $disabled = true; } $suffix = $pofile->getSuffix().'.po'; $choice = new Loco_mvc_ViewParams( [ 'checked' => '', 'writable' => $writable, 'disabled' => $disabled, 'systype' => $systype, 'dirtype' => $typeId, 'parent' => Loco_mvc_FileParams::create($parent), 'hidden' => $pofile->getRelativePath($content_dir), 'holder' => str_replace( $suffix, '<span>{locale}</span>.po', $pofile->basename() ), 'copying' => $sourcedir === $parent->getPath(), 'exists' => $copying && $pofile->exists(), ] ); $sortable[] = $choice; $locations[$typeId]['paths'][] = $choice; }
// display locations in runtime preference order uksort( $locations, [__CLASS__,'compareLocationKeys'] ); $this->set( 'locations', $locations );
// pre-select best option, excluding the current source if copying $preferred = $this->sortPreferred( $sortable ); if( $preferred instanceof ArrayAccess ){ $preferred['checked'] = 'checked'; } // else show total lock message. probably file mods disallowed else if( $fs_failure ){ $this->set('fsLocked', $fs_failure ); } // hidden fields to pass through to Ajax endpoint $this->set('hidden', new Loco_mvc_HiddenFields( [ 'action' => 'loco_json', 'route' => 'msginit', 'loco-nonce' => $this->setNonce('msginit')->value, 'type' => $bundle->getType(), 'bundle' => $bundle->getHandle(), 'domain' => $project ? $project->getId() : '', 'source' => $potpath, ] ) ); $this->set('help', new Loco_mvc_ViewParams( [ 'href' => apply_filters('loco_external','https://localise.biz/wordpress/plugin/manual/msginit'), 'text' => __("What's this?",'loco-translate'), ] ) ); // add a prompt if there's a better option for creating a new PO if( ! $copying ){ if( array_key_exists('copy',$prompts) ){ $prompt = $prompts['copy']; // translators: %s will be replaced with a PO file name $prompt['text'] = sprintf( __('Copy %s instead','loco-translate'), $prompt->__get('name') ); } else { $prompt = $prompts['xgettext']; $prompt['text'] = __('Create template instead','loco-translate'); } if( $extracting ){ $prompt['title'] = __("You're creating translations directly from source code",'loco-translate'); $this->set('prompt',$prompt); } else if( array_key_exists('copy',$prompts) ){ $prompt['title'] = __("You're creating translations from a POT file",'loco-translate'); $this->set('prompt',$prompt); } }
// file system prompts will be handled when paths are selected (i.e. we don't have one yet) $this->prepareFsConnect( 'create', '' ); $this->enqueueScript('poinit'); return $this->view('admin/init/init-po'); }
}
|