Preferred Languages - Version 1.1.0

Version Description

  • New: Support for just-in-time loading of translations.
  • New: Keyboard shortcut for making inactive locales active.
  • Fixed: Responsive design improvements.
  • Fixed: Worked around a few edge cases with the various controls.
  • Fixed: Added missing text domains.
Download this release

Release Info

Developer swissspidy
Plugin Icon Preferred Languages
Version 1.1.0
Comparing to
See all releases

Code changes from version 1.0.1 to 1.1.0

css/preferred-languages-rtl.css CHANGED
@@ -1 +1 @@
1
- .active-locales-list,.inactive-locales-list{width:25em;margin-left:1em;clear:both;float:right}.active-locales-list{height:98px;overflow-y:scroll;margin-top:0;background:#fff;box-shadow:inset 0 1px 2px rgba(0,0,0,.07);border:1px solid #ddd;color:#32373c;box-sizing:border-box}.active-locales-list.empty-list{background:hsla(0,0%,100%,.5);border-color:hsla(0,0%,87%,.75);box-shadow:inset 0 1px 2px rgba(0,0,0,.04);color:rgba(51,51,51,.5);display:-webkit-box;display:flex;-webkit-box-align:center;align-items:center}.active-locales-list li{box-sizing:border-box;width:100%;height:28px;line-height:28px;margin:0;padding:0 8px 0 0;cursor:pointer}.active-locales-list li:hover{background:rgba(0,0,0,.07)}.active-locales-list li[aria-selected=true]{background:rgba(0,0,0,.15)}#active-locales-list-empty-message{background:transparent;text-align:center;height:auto;cursor:default}.inactive-locales{clear:both}.inactive-locales select{width:100%}
1
+ .active-locales{margin:1em 0}.active-locales-list,.inactive-locales-list{width:25em;margin:0 0 1em 1em;clear:both;float:right}.active-locales-controls{clear:both}.active-locales-controls .button,.active-locales-controls ul{margin:0}@media screen and (max-width:782px){.inactive-locales-controls .button{height:40px}}@media screen and (min-width:510px){.active-locales-controls{clear:none}}.active-locales-list{height:98px;overflow-y:scroll;margin-top:0;background:#fff;box-shadow:inset 0 1px 2px rgba(0,0,0,.07);border:1px solid #ddd;color:#32373c;box-sizing:border-box}.active-locales-list.empty-list{background:hsla(0,0%,100%,.5);border-color:hsla(0,0%,87%,.75);box-shadow:inset 0 1px 2px rgba(0,0,0,.04);color:rgba(51,51,51,.5);display:-webkit-box;display:flex;-webkit-box-align:center;align-items:center}.active-locales-list li{box-sizing:border-box;width:100%;height:28px;line-height:28px;margin:0;padding:0 8px 0 0;cursor:pointer}.active-locales-list li:hover{background:rgba(0,0,0,.07)}.active-locales-list li[aria-selected=true]{background:rgba(0,0,0,.15)}#active-locales-list-empty-message{background:transparent;text-align:center;height:auto;cursor:default}.inactive-locales{clear:both}.inactive-locales select{width:100%}
css/preferred-languages.css CHANGED
@@ -1 +1 @@
1
- .active-locales-list,.inactive-locales-list{width:25em;margin-right:1em;clear:both;float:left}.active-locales-list{height:98px;overflow-y:scroll;margin-top:0;background:#fff;box-shadow:inset 0 1px 2px rgba(0,0,0,.07);border:1px solid #ddd;color:#32373c;box-sizing:border-box}.active-locales-list.empty-list{background:hsla(0,0%,100%,.5);border-color:hsla(0,0%,87%,.75);box-shadow:inset 0 1px 2px rgba(0,0,0,.04);color:rgba(51,51,51,.5);display:-webkit-box;display:flex;-webkit-box-align:center;align-items:center}.active-locales-list li{box-sizing:border-box;width:100%;height:28px;line-height:28px;margin:0;padding:0 0 0 8px;cursor:pointer}.active-locales-list li:hover{background:rgba(0,0,0,.07)}.active-locales-list li[aria-selected=true]{background:rgba(0,0,0,.15)}#active-locales-list-empty-message{background:transparent;text-align:center;height:auto;cursor:default}.inactive-locales{clear:both}.inactive-locales select{width:100%}
1
+ .active-locales{margin:1em 0}.active-locales-list,.inactive-locales-list{width:25em;margin:0 1em 1em 0;clear:both;float:left}.active-locales-controls{clear:both}.active-locales-controls .button,.active-locales-controls ul{margin:0}@media screen and (max-width:782px){.inactive-locales-controls .button{height:40px}}@media screen and (min-width:510px){.active-locales-controls{clear:none}}.active-locales-list{height:98px;overflow-y:scroll;margin-top:0;background:#fff;box-shadow:inset 0 1px 2px rgba(0,0,0,.07);border:1px solid #ddd;color:#32373c;box-sizing:border-box}.active-locales-list.empty-list{background:hsla(0,0%,100%,.5);border-color:hsla(0,0%,87%,.75);box-shadow:inset 0 1px 2px rgba(0,0,0,.04);color:rgba(51,51,51,.5);display:-webkit-box;display:flex;-webkit-box-align:center;align-items:center}.active-locales-list li{box-sizing:border-box;width:100%;height:28px;line-height:28px;margin:0;padding:0 0 0 8px;cursor:pointer}.active-locales-list li:hover{background:rgba(0,0,0,.07)}.active-locales-list li[aria-selected=true]{background:rgba(0,0,0,.15)}#active-locales-list-empty-message{background:transparent;text-align:center;height:auto;cursor:default}.inactive-locales{clear:both}.inactive-locales select{width:100%}
inc/class-preferred-languages-textdomain-registry.php ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Locale API: WP_Textdomain_Registry class
4
+ *
5
+ * @package WordPress
6
+ * @subpackage i18n
7
+ * @since 1.1.0
8
+ */
9
+
10
+ /**
11
+ * Core class used for registering textdomains
12
+ *
13
+ * @since 1.1.0
14
+ */
15
+ class Preferred_Languages_Textdomain_Registry {
16
+ /**
17
+ * List of domains and their language directory paths.
18
+ *
19
+ * @since 1.1.0
20
+ *
21
+ * @var array
22
+ */
23
+ protected $domains = array();
24
+
25
+ /**
26
+ * Holds a cached list of available .mo files to improve performance.
27
+ *
28
+ * @since 1.1.0
29
+ *
30
+ * @var array
31
+ */
32
+ protected $cached_mofiles;
33
+
34
+ /**
35
+ * Returns the MO file path for a specific domain.
36
+ *
37
+ * @since 1.1.0
38
+ * @access public
39
+ *
40
+ * @param string $domain Text domain.
41
+ *
42
+ * @return string|false|null MO file path or false if there is none available.
43
+ * Null if none have been fetched yet.
44
+ */
45
+ public function get( $domain ) {
46
+ return isset( $this->domains[ $domain ] ) ? $this->domains[ $domain ] : null;
47
+ }
48
+
49
+ /**
50
+ * Sets the MO file path for a specific domain.
51
+ *
52
+ * @since 1.1.0
53
+ * @access public
54
+ *
55
+ * @param string $domain Text domain.
56
+ * @param string $path Language directory path.
57
+ */
58
+ public function set( $domain, $path ) {
59
+ $this->domains[ $domain ] = $path;
60
+ }
61
+
62
+ /**
63
+ * Resets the registry state.
64
+ *
65
+ * @since 1.1.0
66
+ * @access public
67
+ */
68
+ public function reset() {
69
+ $this->cached_mofiles = null;
70
+ $this->domains = array();
71
+ }
72
+
73
+ /**
74
+ * Gets the path to a translation file in the languages directory for the current locale.
75
+ *
76
+ * @since 1.1.0
77
+ * @access public
78
+ *
79
+ * @see _get_path_to_translation_from_lang_dir()
80
+ *
81
+ * @param string $domain Text domain.
82
+ */
83
+ public function get_translation_from_lang_dir( $domain ) {
84
+ if ( null === $this->cached_mofiles ) {
85
+ $this->cached_mofiles = array();
86
+
87
+ $this->fetch_available_mofiles();
88
+ }
89
+
90
+ foreach ( preferred_languages_get_list() as $locale ) {
91
+ $mofile = "{$domain}-{$locale}.mo";
92
+
93
+ $path = WP_LANG_DIR . '/plugins/' . $mofile;
94
+ if ( in_array( $path, $this->cached_mofiles, true ) ) {
95
+ $this->set( $domain, WP_LANG_DIR . '/plugins/' );
96
+
97
+ return;
98
+ }
99
+
100
+ $path = WP_LANG_DIR . '/themes/' . $mofile;
101
+ if ( in_array( $path, $this->cached_mofiles, true ) ) {
102
+ $this->set( $domain, WP_LANG_DIR . '/themes/' );
103
+
104
+ return;
105
+ }
106
+ }
107
+
108
+ $this->set( $domain, false );
109
+ }
110
+
111
+ /**
112
+ * Fetches all available MO files from the plugins and themes language directories.
113
+ *
114
+ * @since 1.1.0
115
+ * @access protected
116
+ *
117
+ * @see _get_path_to_translation_from_lang_dir()
118
+ */
119
+ protected function fetch_available_mofiles() {
120
+ $locations = array(
121
+ WP_LANG_DIR . '/plugins',
122
+ WP_LANG_DIR . '/themes',
123
+ );
124
+
125
+ foreach ( $locations as $location ) {
126
+ $mofiles = glob( $location . '/*.mo' );
127
+
128
+ if ( $mofiles ) {
129
+ $this->cached_mofiles = array_merge( $this->cached_mofiles, $mofiles );
130
+ }
131
+ }
132
+ }
133
+ }
inc/default-filters.php CHANGED
@@ -1,5 +1,9 @@
1
  <?php
2
 
 
 
 
 
3
  add_action( 'init', 'preferred_languages_register_setting' );
4
  add_action( 'init', 'preferred_languages_register_meta' );
5
  add_action( 'init', 'preferred_languages_register_scripts' );
1
  <?php
2
 
3
+ add_action( 'plugins_loaded', 'preferred_languages_init_registry' );
4
+
5
+ add_filter( 'gettext', 'preferred_languages_filter_gettext', 10, 3 );
6
+
7
  add_action( 'init', 'preferred_languages_register_setting' );
8
  add_action( 'init', 'preferred_languages_register_meta' );
9
  add_action( 'init', 'preferred_languages_register_scripts' );
inc/functions.php CHANGED
@@ -189,9 +189,13 @@ function preferred_languages_filter_user_locale( $value, $object_id, $meta_key )
189
  * @return string The modified MO file path.
190
  */
191
  function preferred_languages_load_textdomain_mofile( $mofile ) {
 
 
 
 
192
  $preferred_locales = preferred_languages_get_list();
193
 
194
- if ( empty( $preferred_locales ) || is_readable( $mofile ) ) {
195
  return $mofile;
196
  }
197
 
@@ -233,10 +237,10 @@ function preferred_languages_register_scripts() {
233
  'preferredLanguages',
234
  array(
235
  'l10n' => array(
236
- 'localeAdded' => __( 'Locale added to list' ),
237
- 'localeRemoved' => __( 'Locale removed from list' ),
238
- 'movedUp' => __( 'Locale moved up' ),
239
- 'movedDown' => __( 'Locale moved down' ),
240
  ),
241
  )
242
  );
@@ -247,7 +251,7 @@ function preferred_languages_register_scripts() {
247
  'preferred-languages',
248
  plugin_dir_url( dirname( __FILE__ ) ) . 'css/preferred-languages' . $rtl_suffix . '.css',
249
  array(),
250
- '20171002',
251
  'screen'
252
  );
253
  }
@@ -286,7 +290,7 @@ function preferred_languages_personal_options( $user ) {
286
  ?>
287
  <tr class="user-preferred-languages-wrap">
288
  <th scope="row">
289
- <span id="preferred-languages-label"><?php _e( 'Language' ); ?></span>
290
  </th>
291
  <td>
292
  <?php
@@ -333,7 +337,7 @@ function preferred_languages_display_form( $args = array() ) {
333
  'native_name' => $translation['native_name'],
334
  'lang' => current( $translation['iso'] ),
335
  );
336
- } else if ( 'en_US' !== $locale ) {
337
  $preferred_languages[] = array(
338
  'language' => $locale,
339
  'native_name' => $locale,
@@ -343,6 +347,7 @@ function preferred_languages_display_form( $args = array() ) {
343
  }
344
  ?>
345
  <div class="preferred-languages">
 
346
  <p><?php _e( 'Choose languages for displaying WordPress in, in order of preference.', 'preferred-languages' ); ?></p>
347
  <div class="active-locales">
348
  <ul
@@ -374,7 +379,6 @@ function preferred_languages_display_form( $args = array() ) {
374
  ?>
375
  </li>
376
  </ul>
377
- <input type="hidden" name="preferred_languages" value="<?php echo esc_attr( implode( ',', $args['selected'] ) ); ?>"/>
378
  <div class="active-locales-controls">
379
  <ul>
380
  <li>
@@ -427,3 +431,69 @@ function preferred_languages_display_form( $args = array() ) {
427
  </div>
428
  <?php
429
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189
  * @return string The modified MO file path.
190
  */
191
  function preferred_languages_load_textdomain_mofile( $mofile ) {
192
+ if ( is_readable( $mofile ) ) {
193
+ return $mofile;
194
+ }
195
+
196
  $preferred_locales = preferred_languages_get_list();
197
 
198
+ if ( empty( $preferred_locales ) ) {
199
  return $mofile;
200
  }
201
 
237
  'preferredLanguages',
238
  array(
239
  'l10n' => array(
240
+ 'localeAdded' => __( 'Locale added to list', 'preferred-languages' ),
241
+ 'localeRemoved' => __( 'Locale removed from list', 'preferred-languages' ),
242
+ 'movedUp' => __( 'Locale moved up', 'preferred-languages' ),
243
+ 'movedDown' => __( 'Locale moved down', 'preferred-languages' ),
244
  ),
245
  )
246
  );
251
  'preferred-languages',
252
  plugin_dir_url( dirname( __FILE__ ) ) . 'css/preferred-languages' . $rtl_suffix . '.css',
253
  array(),
254
+ '20171006',
255
  'screen'
256
  );
257
  }
290
  ?>
291
  <tr class="user-preferred-languages-wrap">
292
  <th scope="row">
293
+ <span id="preferred-languages-label"><?php _e( 'Language', 'preferred-languages' ); ?></span>
294
  </th>
295
  <td>
296
  <?php
337
  'native_name' => $translation['native_name'],
338
  'lang' => current( $translation['iso'] ),
339
  );
340
+ } elseif ( 'en_US' !== $locale ) {
341
  $preferred_languages[] = array(
342
  'language' => $locale,
343
  'native_name' => $locale,
347
  }
348
  ?>
349
  <div class="preferred-languages">
350
+ <input type="hidden" name="preferred_languages" value="<?php echo esc_attr( implode( ',', $args['selected'] ) ); ?>"/>
351
  <p><?php _e( 'Choose languages for displaying WordPress in, in order of preference.', 'preferred-languages' ); ?></p>
352
  <div class="active-locales">
353
  <ul
379
  ?>
380
  </li>
381
  </ul>
 
382
  <div class="active-locales-controls">
383
  <ul>
384
  <li>
431
  </div>
432
  <?php
433
  }
434
+
435
+ /**
436
+ * Initializes the class used for registering textdomains.
437
+ *
438
+ * @since 1.1.0
439
+ */
440
+ function preferred_languages_init_registry() {
441
+ global $textdomain_registry;
442
+
443
+ $textdomain_registry = new Preferred_Languages_Textdomain_Registry();
444
+ }
445
+
446
+ /**
447
+ * Filters gettext call to work around limitations in just-in-time loading of translations.
448
+ *
449
+ * @since 1.1.0
450
+ *
451
+ * @param string $translation Translated text.
452
+ * @param string $text Text to translate.
453
+ * @param string $domain Text domain. Unique identifier for retrieving translated strings.
454
+ *
455
+ * @return string Translated text.
456
+ */
457
+ function preferred_languages_filter_gettext( $translation, $text, $domain ) {
458
+ if ( 'default' === $domain ) {
459
+ return $translation;
460
+ }
461
+
462
+ $translations = get_translations_for_domain( $domain );
463
+
464
+ if ( $translations instanceof NOOP_Translations ) {
465
+ /* @var Preferred_Languages_Textdomain_Registry $textdomain_registry */
466
+ global $textdomain_registry;
467
+
468
+ if ( ! $textdomain_registry instanceof Preferred_Languages_Textdomain_Registry ) {
469
+ preferred_languages_init_registry();
470
+ }
471
+
472
+ $path = $textdomain_registry->get( $domain );
473
+
474
+ if ( ! $path ) {
475
+ $textdomain_registry->get_translation_from_lang_dir( $domain );
476
+ }
477
+
478
+ $path = $textdomain_registry->get( $domain );
479
+
480
+ if ( ! $path ) {
481
+ return $translation;
482
+ }
483
+
484
+ $preferred_locales = preferred_languages_get_list();
485
+
486
+ foreach ( $preferred_locales as $locale ) {
487
+ $mofile = "{$path}/{$domain}-{$locale}.mo";
488
+
489
+ if ( load_textdomain( $domain, $mofile ) ) {
490
+ $translations = get_translations_for_domain( $domain );
491
+ $translation = $translations->translate( $text );
492
+
493
+ break;
494
+ }
495
+ }
496
+ }
497
+
498
+ return $translation;
499
+ }
js/preferred-languages.js CHANGED
@@ -3,7 +3,7 @@
3
  (function (wp, settings, $) {
4
  var $activeLocales = $('.active-locales-list');
5
  var $activeLocalesControls = $('.active-locales-controls');
6
- var $inactiveLocales = $('.inactive-locales-list');
7
  var $inactiveLocalesControls = $('.inactive-locales-controls');
8
  var $selectedLocale = $activeLocales.find('li[aria-selected="true"]');
9
  var $inputField = $('input[name="preferred_languages"]');
@@ -20,7 +20,8 @@
20
 
21
  $activeLocalesControls.find('.locales-move-up').attr('disabled', $activeLocales.hasClass('empty-list') || activeLocalesList.first().is(activeLocale));
22
  $activeLocalesControls.find('.locales-move-down').attr('disabled', $activeLocales.hasClass('empty-list') || activeLocalesList.last().is(activeLocale));
23
- $activeLocalesControls.find('.locales-remove').attr('disabled', $activeLocales.hasClass('empty-list'));
 
24
  }
25
 
26
  /**
@@ -32,7 +33,7 @@
32
  */
33
  function toggleLocale(locale) {
34
  var selected = locale.attr('aria-selected');
35
- var newState = !!selected;
36
 
37
  // It's already the current locale, so nothing to do here.
38
  if (true === selected) {
@@ -137,30 +138,31 @@
137
  var locale = $selectedLocale.attr('id');
138
  var $successor = void 0;
139
 
140
- $successor = $selectedLocale.prev(':visible');
141
 
142
  if (0 === $successor.length) {
143
- $successor = $selectedLocale.next(':visible');
144
  }
145
 
146
  // 1. Remove selected locale.
147
  $selectedLocale.remove();
148
 
149
  // 2. Make another locale the current one.
150
- if ($successor.length > 0) {
151
  toggleLocale($successor);
152
  } else {
153
  showEmptyListMessage();
154
  }
155
 
156
- // 3. Update buttons.
157
- changeButtonState($selectedLocale);
 
158
 
159
  // 4. Update hidden input field.
160
  updateHiddenInput();
161
 
162
- // 5. Make visible in dropdown again.
163
- $inactiveLocales.find('select option[value="' + locale + '"]').removeClass('hidden');
164
 
165
  // 6. Announce to assistive technologies.
166
  wp.a11y.speak(settings.l10n.localeRemoved);
@@ -177,14 +179,14 @@
177
  var $newLocale = $('<li/>', { 'id': option.val(), text: option.text(), 'aria-selected': false, 'class': 'active-locale' });
178
  var $successor = void 0;
179
 
180
- $successor = option.prev(':not(.hidden)');
181
 
182
- if (0 === $successor.length) {
183
- $successor = option.next(':not(.hidden)');
184
  }
185
 
186
- if (0 === $successor.length) {
187
- $successor = $inactiveLocales.find('option').first();
188
  }
189
 
190
  // 1. Change selected value in dropdown.
@@ -195,7 +197,7 @@
195
  option.removeAttr('selected').addClass('hidden');
196
 
197
  // It's already in the list of active locales, stop here.
198
- if ($activeLocales.find('#' + option.val()).length > 0) {
199
  return;
200
  }
201
 
@@ -245,7 +247,7 @@
245
  items: ':not(#active-locales-list-empty-message)'
246
  });
247
 
248
- // Arrow key handler.
249
  $activeLocales.on('keydown', function (e) {
250
 
251
  // Up.
@@ -277,9 +279,22 @@
277
  }
278
  });
279
 
 
 
 
 
 
 
 
 
 
 
 
 
 
280
  // Add new locale to list.
281
  $inactiveLocalesControls.find('.locales-add').on('click', function () {
282
- makeLocaleActive($inactiveLocales.find('select option:selected'));
283
  });
284
 
285
  // Select a locale.
3
  (function (wp, settings, $) {
4
  var $activeLocales = $('.active-locales-list');
5
  var $activeLocalesControls = $('.active-locales-controls');
6
+ var $inactiveLocales = $('.inactive-locales-list select');
7
  var $inactiveLocalesControls = $('.inactive-locales-controls');
8
  var $selectedLocale = $activeLocales.find('li[aria-selected="true"]');
9
  var $inputField = $('input[name="preferred_languages"]');
20
 
21
  $activeLocalesControls.find('.locales-move-up').attr('disabled', $activeLocales.hasClass('empty-list') || activeLocalesList.first().is(activeLocale));
22
  $activeLocalesControls.find('.locales-move-down').attr('disabled', $activeLocales.hasClass('empty-list') || activeLocalesList.last().is(activeLocale));
23
+ $activeLocalesControls.find('.locales-remove').attr('disabled', $activeLocales.hasClass('empty-list') || !$selectedLocale.length);
24
+ $inactiveLocalesControls.find('.locales-add').attr('disabled', 'disabled' === $inactiveLocales.attr('disabled'));
25
  }
26
 
27
  /**
33
  */
34
  function toggleLocale(locale) {
35
  var selected = locale.attr('aria-selected');
36
+ var newState = '' === selected ? true : !!selected;
37
 
38
  // It's already the current locale, so nothing to do here.
39
  if (true === selected) {
138
  var locale = $selectedLocale.attr('id');
139
  var $successor = void 0;
140
 
141
+ $successor = $selectedLocale.prevAll(':visible:first');
142
 
143
  if (0 === $successor.length) {
144
+ $successor = $selectedLocale.nextAll(':visible:first');
145
  }
146
 
147
  // 1. Remove selected locale.
148
  $selectedLocale.remove();
149
 
150
  // 2. Make another locale the current one.
151
+ if ($successor.length) {
152
  toggleLocale($successor);
153
  } else {
154
  showEmptyListMessage();
155
  }
156
 
157
+ // 3. Make visible in dropdown again.
158
+ $inactiveLocales.find('select option[value="' + locale + '"]').removeClass('hidden');
159
+ $inactiveLocales.attr('disabled', false);
160
 
161
  // 4. Update hidden input field.
162
  updateHiddenInput();
163
 
164
+ // 5. Update buttons.
165
+ changeButtonState($selectedLocale);
166
 
167
  // 6. Announce to assistive technologies.
168
  wp.a11y.speak(settings.l10n.localeRemoved);
179
  var $newLocale = $('<li/>', { 'id': option.val(), text: option.text(), 'aria-selected': false, 'class': 'active-locale' });
180
  var $successor = void 0;
181
 
182
+ $successor = option.prevAll(':not(.hidden):first');
183
 
184
+ if (!$successor.length) {
185
+ $successor = option.nextAll(':not(.hidden):first');
186
  }
187
 
188
+ if (!$successor.length) {
189
+ $inactiveLocales.attr('disabled', true);
190
  }
191
 
192
  // 1. Change selected value in dropdown.
197
  option.removeAttr('selected').addClass('hidden');
198
 
199
  // It's already in the list of active locales, stop here.
200
+ if ($activeLocales.find('#' + option.val()).length) {
201
  return;
202
  }
203
 
247
  items: ':not(#active-locales-list-empty-message)'
248
  });
249
 
250
+ // Active locales keyboard shortcuts.
251
  $activeLocales.on('keydown', function (e) {
252
 
253
  // Up.
279
  }
280
  });
281
 
282
+ // Inactive Locales keyboard shortcuts.
283
+ $inactiveLocales.on('keydown', function (e) {
284
+
285
+ // Letter "A".
286
+ if (65 === e.which) {
287
+ if (e.altKey) {
288
+ makeLocaleActive($inactiveLocales.find('option:selected'));
289
+ }
290
+
291
+ e.preventDefault();
292
+ }
293
+ });
294
+
295
  // Add new locale to list.
296
  $inactiveLocalesControls.find('.locales-add').on('click', function () {
297
+ makeLocaleActive($inactiveLocales.find('option:selected'));
298
  });
299
 
300
  // Select a locale.
js/preferred-languages.min.js CHANGED
@@ -1 +1 @@
1
- "use strict";(function(wp,settings,$){var $activeLocales=$(".active-locales-list");var $activeLocalesControls=$(".active-locales-controls");var $inactiveLocales=$(".inactive-locales-list");var $inactiveLocalesControls=$(".inactive-locales-controls");var $selectedLocale=$activeLocales.find('li[aria-selected="true"]');var $inputField=$('input[name="preferred_languages"]');function changeButtonState(activeLocale){var activeLocalesList=$activeLocales.find(".active-locale");$activeLocalesControls.find(".locales-move-up").attr("disabled",$activeLocales.hasClass("empty-list")||activeLocalesList.first().is(activeLocale));$activeLocalesControls.find(".locales-move-down").attr("disabled",$activeLocales.hasClass("empty-list")||activeLocalesList.last().is(activeLocale));$activeLocalesControls.find(".locales-remove").attr("disabled",$activeLocales.hasClass("empty-list"))}function toggleLocale(locale){var selected=locale.attr("aria-selected");var newState=!!selected;if(true===selected){return}$selectedLocale.attr("aria-selected",false);locale.attr("aria-selected",newState);if(true===newState){$selectedLocale=locale;$activeLocales.attr("aria-activedescendant",$selectedLocale.attr("id"))}changeButtonState(locale)}function updateHiddenInput(){var locales=[];$activeLocales.children(".active-locale").each(function(index,el){locales.push($(el).attr("id"))});$inputField.val(locales.join(","))}function moveLocaleUp(){$selectedLocale.insertBefore($selectedLocale.prev());updateHiddenInput();changeButtonState($selectedLocale);wp.a11y.speak(settings.l10n.movedUp)}function moveLocaleDown(){$selectedLocale.insertAfter($selectedLocale.next());updateHiddenInput();changeButtonState($selectedLocale);wp.a11y.speak(settings.l10n.movedDown)}function showEmptyListMessage(){$activeLocales.addClass("empty-list");$activeLocales.attr("aria-activedescendant","");$activeLocales.find("#active-locales-list-empty-message").removeClass("hidden")}function hideEmptyListMessage(){$activeLocales.removeClass("empty-list");$activeLocales.find("#active-locales-list-empty-message").addClass("hidden")}function removeActiveLocale(){var locale=$selectedLocale.attr("id");var $successor=void 0;$successor=$selectedLocale.prev(":visible");if(0===$successor.length){$successor=$selectedLocale.next(":visible")}$selectedLocale.remove();if($successor.length>0){toggleLocale($successor)}else{showEmptyListMessage()}changeButtonState($selectedLocale);updateHiddenInput();$inactiveLocales.find('select option[value="'+locale+'"]').removeClass("hidden");wp.a11y.speak(settings.l10n.localeRemoved)}function makeLocaleActive(option){var $newLocale=$("<li/>",{id:option.val(),text:option.text(),"aria-selected":false,class:"active-locale"});var $successor=void 0;$successor=option.prev(":not(.hidden)");if(0===$successor.length){$successor=option.next(":not(.hidden)")}if(0===$successor.length){$successor=$inactiveLocales.find("option").first()}$successor.attr("selected",true);$inactiveLocalesControls.val($successor.val());option.removeAttr("selected").addClass("hidden");if($activeLocales.find("#"+option.val()).length>0){return}if($activeLocales.hasClass("empty-list")){hideEmptyListMessage()}$activeLocales.append($newLocale);toggleLocale($newLocale);$activeLocales.animate({scrollTop:$newLocale.offset().top-$activeLocales.offset().top+$activeLocales.scrollTop()});updateHiddenInput();wp.a11y.speak(settings.l10n.localeAdded)}$(".user-language-wrap").remove();$("#WPLANG").parent().parent().remove();$inactiveLocales.find('[lang="en"]').remove();changeButtonState($selectedLocale);if($inputField.val().length){$.each($inputField.val().split(","),function(index,value){makeLocaleActive($inactiveLocales.find('[value="'+value+'"]'))})}$activeLocales.sortable({axis:"y",cursor:"move",items:":not(#active-locales-list-empty-message)"});$activeLocales.on("keydown",function(e){if(38===e.which){if(e.altKey){moveLocaleUp()}else{$selectedLocale.prev().length&&toggleLocale($selectedLocale.prev())}e.preventDefault()}if(40===e.which){if(e.altKey){moveLocaleDown()}else{$selectedLocale.next().length&&toggleLocale($selectedLocale.next())}e.preventDefault()}if(8===e.which){removeActiveLocale();e.preventDefault()}});$inactiveLocalesControls.find(".locales-add").on("click",function(){makeLocaleActive($inactiveLocales.find("select option:selected"))});$activeLocales.on("click",".active-locale",function(e){toggleLocale($(e.currentTarget))});$activeLocalesControls.find(".locales-move-up").on("click",moveLocaleUp);$activeLocalesControls.find(".locales-move-down").on("click",moveLocaleDown);$activeLocalesControls.find(".locales-remove").on("click",removeActiveLocale)})(wp,preferredLanguages,jQuery);
1
+ "use strict";(function(wp,settings,$){var $activeLocales=$(".active-locales-list");var $activeLocalesControls=$(".active-locales-controls");var $inactiveLocales=$(".inactive-locales-list select");var $inactiveLocalesControls=$(".inactive-locales-controls");var $selectedLocale=$activeLocales.find('li[aria-selected="true"]');var $inputField=$('input[name="preferred_languages"]');function changeButtonState(activeLocale){var activeLocalesList=$activeLocales.find(".active-locale");$activeLocalesControls.find(".locales-move-up").attr("disabled",$activeLocales.hasClass("empty-list")||activeLocalesList.first().is(activeLocale));$activeLocalesControls.find(".locales-move-down").attr("disabled",$activeLocales.hasClass("empty-list")||activeLocalesList.last().is(activeLocale));$activeLocalesControls.find(".locales-remove").attr("disabled",$activeLocales.hasClass("empty-list")||!$selectedLocale.length);$inactiveLocalesControls.find(".locales-add").attr("disabled","disabled"===$inactiveLocales.attr("disabled"))}function toggleLocale(locale){var selected=locale.attr("aria-selected");var newState=""===selected?true:!!selected;if(true===selected){return}$selectedLocale.attr("aria-selected",false);locale.attr("aria-selected",newState);if(true===newState){$selectedLocale=locale;$activeLocales.attr("aria-activedescendant",$selectedLocale.attr("id"))}changeButtonState(locale)}function updateHiddenInput(){var locales=[];$activeLocales.children(".active-locale").each(function(index,el){locales.push($(el).attr("id"))});$inputField.val(locales.join(","))}function moveLocaleUp(){$selectedLocale.insertBefore($selectedLocale.prev());updateHiddenInput();changeButtonState($selectedLocale);wp.a11y.speak(settings.l10n.movedUp)}function moveLocaleDown(){$selectedLocale.insertAfter($selectedLocale.next());updateHiddenInput();changeButtonState($selectedLocale);wp.a11y.speak(settings.l10n.movedDown)}function showEmptyListMessage(){$activeLocales.addClass("empty-list");$activeLocales.attr("aria-activedescendant","");$activeLocales.find("#active-locales-list-empty-message").removeClass("hidden")}function hideEmptyListMessage(){$activeLocales.removeClass("empty-list");$activeLocales.find("#active-locales-list-empty-message").addClass("hidden")}function removeActiveLocale(){var locale=$selectedLocale.attr("id");var $successor=void 0;$successor=$selectedLocale.prevAll(":visible:first");if(0===$successor.length){$successor=$selectedLocale.nextAll(":visible:first")}$selectedLocale.remove();if($successor.length){toggleLocale($successor)}else{showEmptyListMessage()}$inactiveLocales.find('select option[value="'+locale+'"]').removeClass("hidden");$inactiveLocales.attr("disabled",false);updateHiddenInput();changeButtonState($selectedLocale);wp.a11y.speak(settings.l10n.localeRemoved)}function makeLocaleActive(option){var $newLocale=$("<li/>",{id:option.val(),text:option.text(),"aria-selected":false,class:"active-locale"});var $successor=void 0;$successor=option.prevAll(":not(.hidden):first");if(!$successor.length){$successor=option.nextAll(":not(.hidden):first")}if(!$successor.length){$inactiveLocales.attr("disabled",true)}$successor.attr("selected",true);$inactiveLocalesControls.val($successor.val());option.removeAttr("selected").addClass("hidden");if($activeLocales.find("#"+option.val()).length){return}if($activeLocales.hasClass("empty-list")){hideEmptyListMessage()}$activeLocales.append($newLocale);toggleLocale($newLocale);$activeLocales.animate({scrollTop:$newLocale.offset().top-$activeLocales.offset().top+$activeLocales.scrollTop()});updateHiddenInput();wp.a11y.speak(settings.l10n.localeAdded)}$(".user-language-wrap").remove();$("#WPLANG").parent().parent().remove();$inactiveLocales.find('[lang="en"]').remove();changeButtonState($selectedLocale);if($inputField.val().length){$.each($inputField.val().split(","),function(index,value){makeLocaleActive($inactiveLocales.find('[value="'+value+'"]'))})}$activeLocales.sortable({axis:"y",cursor:"move",items:":not(#active-locales-list-empty-message)"});$activeLocales.on("keydown",function(e){if(38===e.which){if(e.altKey){moveLocaleUp()}else{$selectedLocale.prev().length&&toggleLocale($selectedLocale.prev())}e.preventDefault()}if(40===e.which){if(e.altKey){moveLocaleDown()}else{$selectedLocale.next().length&&toggleLocale($selectedLocale.next())}e.preventDefault()}if(8===e.which){removeActiveLocale();e.preventDefault()}});$inactiveLocales.on("keydown",function(e){if(65===e.which){if(e.altKey){makeLocaleActive($inactiveLocales.find("option:selected"))}e.preventDefault()}});$inactiveLocalesControls.find(".locales-add").on("click",function(){makeLocaleActive($inactiveLocales.find("option:selected"))});$activeLocales.on("click",".active-locale",function(e){toggleLocale($(e.currentTarget))});$activeLocalesControls.find(".locales-move-up").on("click",moveLocaleUp);$activeLocalesControls.find(".locales-move-down").on("click",moveLocaleDown);$activeLocalesControls.find(".locales-remove").on("click",removeActiveLocale)})(wp,preferredLanguages,jQuery);
preferred-languages.php CHANGED
@@ -3,7 +3,7 @@
3
  * Plugin Name: Preferred Languages
4
  * Plugin URI: https://github.com/swissspidy/preferred-languages/
5
  * Description: Choose languages for displaying WordPress in, in order of preference.
6
- * Version: 1.0.1
7
  * Author: Pascal Birchler
8
  * Author URI: https://pascalbirchler.com
9
  * License: GPL-2.0+
@@ -28,6 +28,7 @@
28
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
29
  */
30
 
 
31
  require_once( dirname( __FILE__ ) . '/inc/functions.php' );
32
  require_once( dirname( __FILE__ ) . '/inc/default-filters.php' );
33
 
3
  * Plugin Name: Preferred Languages
4
  * Plugin URI: https://github.com/swissspidy/preferred-languages/
5
  * Description: Choose languages for displaying WordPress in, in order of preference.
6
+ * Version: 1.1.0
7
  * Author: Pascal Birchler
8
  * Author URI: https://pascalbirchler.com
9
  * License: GPL-2.0+
28
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
29
  */
30
 
31
+ require_once( dirname( __FILE__ ) . '/inc/class-preferred-languages-textdomain-registry.php' );
32
  require_once( dirname( __FILE__ ) . '/inc/functions.php' );
33
  require_once( dirname( __FILE__ ) . '/inc/default-filters.php' );
34
 
readme.txt CHANGED
@@ -2,9 +2,9 @@
2
  Contributors: swissspidy
3
  Tags: internationalization, i18n, localization, l10n, language, locale, translation
4
  Requires at least: 4.7
5
- Tested up to: 4.8
6
  Requires PHP: 5.2
7
- Stable tag: 1.0.1
8
  License: GPLv2 or later
9
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
10
 
@@ -21,20 +21,34 @@ This feature project aims to change that by letting users choose multiple langua
21
  1. The new language section in 'Settings' -> 'General'
22
  2. The new language section in your user profile.
23
 
24
- == Upgrade Notice ==
 
 
 
 
 
 
 
 
25
 
26
  = 1.0.1 =
27
 
28
- This release fixes a bug that prevented saving changes in some cases.
29
 
30
  = 1.0.0 =
31
 
32
- Initial release.
33
 
34
- == Changelog ==
 
 
 
 
35
 
36
  = 1.0.1 =
37
- * Fixed: Fixed a bug that prevented saving changes.
 
38
 
39
  = 1.0.0 =
40
- * Initial release.
 
2
  Contributors: swissspidy
3
  Tags: internationalization, i18n, localization, l10n, language, locale, translation
4
  Requires at least: 4.7
5
+ Tested up to: 4.9
6
  Requires PHP: 5.2
7
+ Stable tag: 1.1.0
8
  License: GPLv2 or later
9
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
10
 
21
  1. The new language section in 'Settings' -> 'General'
22
  2. The new language section in your user profile.
23
 
24
+ == Changelog ==
25
+
26
+ = 1.1.0 =
27
+
28
+ * New: Support for just-in-time loading of translations.
29
+ * New: Keyboard shortcut for making inactive locales active.
30
+ * Fixed: Responsive design improvements.
31
+ * Fixed: Worked around a few edge cases with the various controls.
32
+ * Fixed: Added missing text domains.
33
 
34
  = 1.0.1 =
35
 
36
+ * Fixed: Fixed a bug that prevented saving changes.
37
 
38
  = 1.0.0 =
39
 
40
+ * Initial release.
41
 
42
+ == Upgrade Notice ==
43
+
44
+ = 1.1.0 =
45
+
46
+ This release includes some accessibility and usability improvements, as well as support for just-in-time loading of translations.
47
 
48
  = 1.0.1 =
49
+
50
+ This release fixes a bug that prevented saving changes in some cases.
51
 
52
  = 1.0.0 =
53
+
54
+ Initial release.