From f1ebafff173668aa578f8a604234aef29c377eec Mon Sep 17 00:00:00 2001 From: Tom Hughes Date: Sun, 24 Jul 2022 12:11:19 +0100 Subject: [PATCH] Add a locale selector --- .rubocop_todo.yml | 2 +- Vendorfile | 8 + app/assets/javascripts/application.js | 1 + app/assets/javascripts/osm.js.erb | 1 + app/assets/javascripts/uls.js | 30 + app/assets/stylesheets/screen-ltr.scss | 1 + app/assets/stylesheets/screen-rtl.rtlcss.scss | 1 + app/assets/stylesheets/uls.scss | 42 + app/controllers/application_controller.rb | 2 + app/controllers/users_controller.rb | 6 +- app/views/layouts/_header.html.erb | 2 + vendor/assets/jquery.uls/css/jquery.uls.css | 119 + .../assets/jquery.uls/css/jquery.uls.grid.css | 307 + .../assets/jquery.uls/css/jquery.uls.lcd.css | 140 + .../jquery.uls/css/jquery.uls.mobile.css | 307 + vendor/assets/jquery.uls/images/close.svg | 8 + vendor/assets/jquery.uls/images/language.svg | 7 + vendor/assets/jquery.uls/images/search.svg | 7 + .../assets/jquery.uls/src/jquery.uls.core.js | 499 ++ .../assets/jquery.uls/src/jquery.uls.data.js | 6412 +++++++++++++++++ .../jquery.uls/src/jquery.uls.data.utils.js | 335 + .../src/jquery.uls.languagefilter.js | 361 + .../assets/jquery.uls/src/jquery.uls.lcd.js | 481 ++ 23 files changed, 9077 insertions(+), 2 deletions(-) create mode 100644 app/assets/javascripts/uls.js create mode 100644 app/assets/stylesheets/uls.scss create mode 100644 vendor/assets/jquery.uls/css/jquery.uls.css create mode 100644 vendor/assets/jquery.uls/css/jquery.uls.grid.css create mode 100644 vendor/assets/jquery.uls/css/jquery.uls.lcd.css create mode 100644 vendor/assets/jquery.uls/css/jquery.uls.mobile.css create mode 100644 vendor/assets/jquery.uls/images/close.svg create mode 100644 vendor/assets/jquery.uls/images/language.svg create mode 100644 vendor/assets/jquery.uls/images/search.svg create mode 100644 vendor/assets/jquery.uls/src/jquery.uls.core.js create mode 100644 vendor/assets/jquery.uls/src/jquery.uls.data.js create mode 100644 vendor/assets/jquery.uls/src/jquery.uls.data.utils.js create mode 100644 vendor/assets/jquery.uls/src/jquery.uls.languagefilter.js create mode 100644 vendor/assets/jquery.uls/src/jquery.uls.lcd.js diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index deaea8e2da..a45b3e1869 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -61,7 +61,7 @@ Metrics/BlockNesting: # Offense count: 26 # Configuration parameters: CountComments, CountAsOne. Metrics/ClassLength: - Max: 299 + Max: 315 # Offense count: 59 # Configuration parameters: AllowedMethods, AllowedPatterns. diff --git a/Vendorfile b/Vendorfile index 4c6a80839d..c98857db4b 100644 --- a/Vendorfile +++ b/Vendorfile @@ -45,4 +45,12 @@ folder 'vendor/assets' do folder 'polyfill' do file 'es6.js', 'https://polyfill.io/v3/polyfill.js?features=es6&flags=gated,always' end + + folder 'jquery.uls' do + from 'https://github.com/wikimedia/jquery.uls.git' do + folder 'css' + folder 'src' + folder 'images' + end + end end diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 0bfff869eb..822dc9836c 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -18,6 +18,7 @@ //= require qs/dist/qs //= require bs-custom-file-input //= require bs-custom-file-input-init +//= require uls /* * Called as the user scrolls/zooms around to manipulate hrefs of the diff --git a/app/assets/javascripts/osm.js.erb b/app/assets/javascripts/osm.js.erb index 2e33438dbd..e9d9eb3316 100644 --- a/app/assets/javascripts/osm.js.erb +++ b/app/assets/javascripts/osm.js.erb @@ -21,6 +21,7 @@ OSM = { FOSSGIS_OSRM_URL: <%= Settings.fossgis_osrm_url.to_json %>, FOSSGIS_VALHALLA_URL: <%= Settings.fossgis_valhalla_url.to_json %>, DEFAULT_LOCALE: <%= I18n.default_locale.to_json %>, + AVAILABLE_LOCALES: <%= I18n.available_locales.map(&:to_s).to_json %>, <% if Settings.key?(:thunderforest_key) %> THUNDERFOREST_KEY: <%= Settings.thunderforest_key.to_json %>, diff --git a/app/assets/javascripts/uls.js b/app/assets/javascripts/uls.js new file mode 100644 index 0000000000..93cc627177 --- /dev/null +++ b/app/assets/javascripts/uls.js @@ -0,0 +1,30 @@ +//= require src/jquery.uls.data +//= require src/jquery.uls.data.utils +//= require src/jquery.uls.lcd +//= require src/jquery.uls.languagefilter +//= require src/jquery.uls.core + +$(document).ready(function () { + function updateLanguage(language) { + Cookies.set("_osm_locale", language, { secure: true, path: "/", samesite: "lax" }); + + document.location.reload(); + } + + var languages = $.uls.data.getAutonyms(); + + for (var code in languages) { + if (!OSM.AVAILABLE_LOCALES.includes(code)) { + delete languages[code]; + } + } + + $(".uls-trigger").uls({ + onSelect: updateLanguage, + languages: languages + }); + + var application_data = $("head").data(); + + $(".uls-trigger").text(Cookies.get("_osm_locale") || application_data.locale); +}); diff --git a/app/assets/stylesheets/screen-ltr.scss b/app/assets/stylesheets/screen-ltr.scss index c525060af5..9bb84bad01 100644 --- a/app/assets/stylesheets/screen-ltr.scss +++ b/app/assets/stylesheets/screen-ltr.scss @@ -1 +1,2 @@ @import "common"; +@import "uls"; diff --git a/app/assets/stylesheets/screen-rtl.rtlcss.scss b/app/assets/stylesheets/screen-rtl.rtlcss.scss index c525060af5..9bb84bad01 100644 --- a/app/assets/stylesheets/screen-rtl.rtlcss.scss +++ b/app/assets/stylesheets/screen-rtl.rtlcss.scss @@ -1 +1,2 @@ @import "common"; +@import "uls"; diff --git a/app/assets/stylesheets/uls.scss b/app/assets/stylesheets/uls.scss new file mode 100644 index 0000000000..54e49036e9 --- /dev/null +++ b/app/assets/stylesheets/uls.scss @@ -0,0 +1,42 @@ +/* + *= require css/jquery.uls + *= require css/jquery.uls.grid + *= require css/jquery.uls.lcd + */ + +.uls-menu { + z-index: 100000; + + h1 { + margin: 16px 0px; + } + + .row { + display: block; + + * { + width: auto; + max-width: none; + } + + li { + width: max-content; + } + } +} + +.uls-trigger { + cursor: pointer; + background-image: image-url('images/language.png'); + background-image: linear-gradient(transparent, transparent), image-url('images/language.svg'); +} + +.uls-icon-close { + background-image: image-url('images/close.png'); + background-image: linear-gradient(transparent, transparent), image-url('images/close.svg'); +} + +.uls-menu .uls-search-label { + background-image: image-rl('images/search.png'); + background-image: linear-gradient(transparent, transparent), image-url('images/search.svg'); +} diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index c830d4bcd3..a25b0a3c91 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -175,6 +175,8 @@ def report_error(message, status = :bad_request) def preferred_languages @preferred_languages ||= if params[:locale] Locale.list(params[:locale]) + elsif request.cookies["_osm_locale"] + Locale.list(request.cookies["_osm_locale"]) elsif current_user current_user.preferred_languages else diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 961be40246..b6d601c573 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -179,7 +179,11 @@ def save current_user.data_public = true current_user.description = "" if current_user.description.nil? current_user.creation_ip = request.remote_ip - current_user.languages = http_accept_language.user_preferred_languages + current_user.languages = if request.cookies["_osm_locale"] + Locale.list(request.cookies["_osm_locale"]) + else + http_accept_language.user_preferred_languages + end current_user.terms_agreed = Time.now.utc current_user.tou_agreed = Time.now.utc current_user.terms_seen = true diff --git a/app/views/layouts/_header.html.erb b/app/views/layouts/_header.html.erb index e9ded0f75c..773c0c9a6f 100644 --- a/app/views/layouts/_header.html.erb +++ b/app/views/layouts/_header.html.erb @@ -41,6 +41,7 @@ <% end -%> <% end %> +
  • @@ -70,6 +71,7 @@ <% end -%> <% end %> +
  • <%= link_to t("layouts.gps_traces"), traces_path, :class => "dropdown-item" %>
  • <%= link_to t("layouts.user_diaries"), diary_entries_path, :class => "dropdown-item" %>
  • <%= link_to t("layouts.communities"), communities_path, :class => "dropdown-item" %>
  • diff --git a/vendor/assets/jquery.uls/css/jquery.uls.css b/vendor/assets/jquery.uls/css/jquery.uls.css new file mode 100644 index 0000000000..e589101380 --- /dev/null +++ b/vendor/assets/jquery.uls/css/jquery.uls.css @@ -0,0 +1,119 @@ +.uls-trigger { + background: url( ../images/language.svg ) no-repeat left center; + padding-left: 24px; +} + +.uls-menu { + position: absolute; + z-index: 1000; + display: none; + margin-top: 1px; + background-color: #fff; + border: 1px solid #ccc; + border-color: rgba( 0, 0, 0, 0.2 ); + -webkit-box-shadow: 0 5px 10px rgba( 0, 0, 0, 0.2 ); + -moz-box-shadow: 0 5px 10px rgba( 0, 0, 0, 0.2 ); + box-shadow: 0 5px 10px rgba( 0, 0, 0, 0.2 ); + -webkit-background-clip: padding-box; + -moz-background-clip: padding; + background-clip: padding-box; +} + +.uls-wide { + min-width: 715px; +} + +.uls-medium { + min-width: 360px; +} + +/* Override the grid */ +.uls-medium.grid .row { + min-width: 300px; +} + +.uls-narrow { + min-width: 180px; +} + +/* Override the grid */ +.uls-narrow.grid .row { + min-width: 150px; +} + +.uls-search { + background-color: #fff; + padding: 5px 16px; + border-bottom: 1px solid #ddd; +} + +.grid .uls-search { + padding-left: 0; +} + +/* Make space for magnifying class on the front */ +.uls-search-wrapper { + position: relative; + padding-left: 44px; + margin-right: 5px; + height: 32px; +} + +.uls-search-label { + background: url( ../images/search.svg ) no-repeat center center; + background-size: 20px; + height: 32px; + width: 44px; + display: block; + position: absolute; + left: 0; + opacity: 0.5; +} + +.uls-search-input-wrapper { + position: relative; +} + +/* There are two input boxes. This class applies to both of them */ +.uls-filterinput { + font-size: 1.143em; + height: 32px; + width: 100%; + /* For the custom clear (X) icon */ + padding: 6px 25px 6px 0; + outline: 0; + border: 0; + display: block; + position: absolute; + top: 0; + left: 0; +} + +/* This is the actual input */ +.uls-languagefilter { + background-color: transparent; + border: 0; + color: #222; +} + +/* This is the shadow input box showing completion suggestions */ +.uls-filtersuggestion { + background-color: #fff; + color: #777; +} + +.uls-languagefilter-clear { + background: url( ../images/close.svg ) no-repeat left center; + background-size: 15px; + cursor: pointer; + height: 15px; + width: 15px; + opacity: 0.7; + + /* Vertical margins: (32 - 15) / 2 */ + margin: 8.5px 5px; + position: absolute; + right: 0; + /* Make it appear above the input boxes */ + z-index: 1; +} diff --git a/vendor/assets/jquery.uls/css/jquery.uls.grid.css b/vendor/assets/jquery.uls/css/jquery.uls.grid.css new file mode 100644 index 0000000000..bd8a174612 --- /dev/null +++ b/vendor/assets/jquery.uls/css/jquery.uls.grid.css @@ -0,0 +1,307 @@ +/* Generated using Foundation http://foundation.zurb.com/docs/grid.php */ +/* Global Reset & Standards ---------------------- */ +.grid * { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +/* Misc ---------------------- */ +.grid .left { + float: left; +} + +.grid .right { + float: right; +} + +.grid .text-left { + text-align: left; +} + +.grid .text-right { + text-align: right; +} + +.grid .text-center { + text-align: center; +} + +.grid .hide { + display: none; +} + +.grid .highlight { + background: #ff9; +} + +/* The Grid ---------------------- */ + +.grid .row { + width: 100%; + max-width: none; + min-width: 600px; + margin: 0 auto; +} + +.grid .row .row { + width: auto; + max-width: none; + min-width: 0; + margin: 0 -5px; +} + +.grid .column, +.grid .columns { + float: left; + min-height: 1px; + padding: 0 5px; + position: relative; +} + +.grid .row.collapse .column, +.grid .row.collapse .columns { + padding: 0; +} + +.grid .row .row.collapse { + margin: 0; +} + +.grid .column.centered, +.grid .columns.centered { + float: none; + margin: 0 auto; +} + +.grid .row .one { + width: 8.333%; +} + +.grid .row .two { + width: 16.667%; +} + +.grid .row .three { + width: 25%; +} + +.grid .row .four { + width: 33.333%; +} + +.grid .row .five { + width: 41.667%; +} + +.grid .row .six { + width: 50%; +} + +.grid .row .seven { + width: 58.333%; +} + +.grid .row .eight { + width: 66.667%; +} + +.grid .row .nine { + width: 75%; +} + +.grid .row .ten { + width: 83.333%; +} + +.grid .row .eleven { + width: 91.667%; +} + +.grid .row .twelve { + width: 100%; +} + +.grid .row .offset-by-one { + margin-left: 8.333%; +} + +.grid .row .offset-by-two { + margin-left: 16.667%; +} + +.grid .row .offset-by-three { + margin-left: 25%; +} + +.grid .row .offset-by-four { + margin-left: 33.333%; +} + +.grid .row .offset-by-five { + margin-left: 41.667%; +} + +.grid .row .offset-by-six { + margin-left: 50%; +} + +.grid .row .offset-by-seven { + margin-left: 58.333%; +} + +.grid .row .offset-by-eight { + margin-left: 66.667%; +} + +.grid .row .offset-by-nine { + margin-left: 75%; +} + +.grid .row .offset-by-ten { + margin-left: 83.333%; +} + +.grid .push-two { + left: 16.667%; +} + +.grid .pull-two { + right: 16.667%; +} + +.grid .push-three { + left: 25%; +} + +.grid .pull-three { + right: 25%; +} + +.grid .push-four { + left: 33.333%; +} + +.grid .pull-four { + right: 33.333%; +} + +.grid .push-five { + left: 41.667%; +} + +.grid .pull-five { + right: 41.667%; +} + +.grid .push-six { + left: 50%; +} + +.grid .pull-six { + right: 50%; +} + +.grid .push-seven { + left: 58.333%; +} + +.grid .pull-seven { + right: 58.333%; +} + +.grid .push-eight { + left: 66.667%; +} + +.grid .pull-eight { + right: 66.667%; +} + +.grid .push-nine { + left: 75%; +} + +.grid .pull-nine { + right: 75%; +} + +.grid .push-ten { + left: 83.333%; +} + +.grid .pull-ten { + right: 83.333%; +} + +.grid .row:before, +.grid .row:after { + content: ''; + display: table; +} + +.grid .row:after { + clear: both; +} + +/* Block Grids ---------------------- */ +/* These are 2-up, 3-up, 4-up and 5-up ULs, suited + for repeating blocks of content. Add 'mobile' to + them to switch them just like the layout grid + (one item per line) on phones + + For IE7/8 compatibility block-grid items need to be + the same height. You can optionally uncomment the + lines below to support arbitrary height, but know + that IE7/8 do not support :nth-child. + -------------------------------------------------- */ +.grid .block-grid { + display: block; + overflow: hidden; + padding: 0; +} + +.grid .block-grid > li { + display: block; + height: auto; + float: left; +} + +.grid .block-grid.two-up { + margin: 0 -15px; +} + +.grid .block-grid.two-up > li { + width: 50%; + padding: 0 15px 15px; +} + +/* .block-grid.two-up>li:nth-child(2n+1) {clear: left;} */ +.grid .block-grid.three-up { + margin: 0 -12px; +} + +.grid .block-grid.three-up > li { + width: 33.33%; + padding: 0 12px 12px; +} + +/* .block-grid.three-up>li:nth-child(3n+1) {clear: left;} */ +.grid .block-grid.four-up { + margin: 0 -10px; +} + +.grid .block-grid.four-up > li { + width: 25%; + padding: 0 10px 10px; +} + +/* .block-grid.four-up>li:nth-child(4n+1) {clear: left;} */ +.grid .block-grid.five-up { + margin: 0 -8px; +} + +.grid .block-grid.five-up > li { + width: 20%; + padding: 0 8px 8px; +} diff --git a/vendor/assets/jquery.uls/css/jquery.uls.lcd.css b/vendor/assets/jquery.uls/css/jquery.uls.lcd.css new file mode 100644 index 0000000000..4b04e1392f --- /dev/null +++ b/vendor/assets/jquery.uls/css/jquery.uls.lcd.css @@ -0,0 +1,140 @@ +/* + * Language Category Display (LCD) consists of multiple sections: + * - one to display when there are no search results (which might display suggested languages) + * - one for each region of the world, plus regions for world languages and suggested languages + * The regions consist of one or more rows (blocks) which consist of one-to-four columns. Each + * column is an ul element, and contains up to 8 li elements, each containing a link. + */ + +.uls-lcd { + background-color: #fcfcfc; + height: 20em; + /* Work around Chrome bug where it places scrollbar on the left in + * in RTL mode but actually reserves the place on the right side */ + overflow-x: hidden; + overflow-y: auto; + width: auto; + padding: 0 16px; +} + +.uls-lcd-region-title { + color: #555; + font-size: 1em; + padding-left: 28px; +} + +.uls-lcd--no-quicklist [ data-region='all' ] .uls-lcd-region-title { + display: none; +} + +.uls-lcd-region-section { + margin-top: 10px; +} + +/* + * We need to have this as a grid row to push rest of the content below it, but resetting + * padding and margin so that calculating them for children is easier. + */ +.grid .uls-language-block.row { + padding: 0; + margin: 0; +} + +.uls-language-block > ul { + /* + * We don't want any visible bullets in this list. Not by default anyway. + * Using very unspecific selector here to allow other classes to override. + * Bug because overflow: hidden is incompatible with bullets, also render + * the bullets inside the list in case there should be any. + */ + list-style: none none; +} + +/* + * Each block should have 16px padding on both sides. But because we already gave + * 16px for the whole menu, we need to remove it for first and last items the blocks. + */ +.grid .uls-language-block > ul { + margin: 0 0 20px 0; + padding: 0 16px; +} + +.grid .uls-language-block > ul:first-child { + padding-left: 0; +} + +.grid .uls-language-block > ul:last-child { + padding-right: 0; +} + +.uls-language-block > ul > li { + margin-left: 20px; + padding: 0; + /* + * The directionality (ltr/rtl) for each list item is set dynamically + * as HTML attributes in JavaScript. Setting directionality also applies + * alignment, but a list with mixed alignment is hard to read. + * All items are therefore explicitly aligned to the left, including names + * of right-to-left languages in left-to-right environment and vice versa. + * As long as the directionality of the item is set correctly, the text + * is readable. + */ + text-align: left; +} + +.uls-language-block > ul > li:hover { + background-color: #eaeff7; +} + +.uls-language-block a { + cursor: pointer; + text-decoration: none; + color: #36c; + font-size: 1em; + display: inline-block; + width: 100%; + overflow-x: hidden; + padding: 8px; + /* + * Some languages have long names for various reasons and we still want + * them to appear on one line. + * To make it work correctly, the directionality must be set correctly + * on the item level. + */ + text-overflow: ellipsis; + white-space: nowrap; + vertical-align: middle; +} + +.uls-no-results-view { + display: none; +} + +.uls-lcd.uls-no-results > .uls-lcd-region-section { + display: none; +} + +.uls-lcd.uls-no-results > .uls-no-results-view { + display: block; +} + +.uls-no-results-found-title { + font-size: 1.143em; + padding: 0 16px 0 28px; + margin: 20px 0; + border-bottom: 0; + color: #54595d; +} + +.uls-no-found-more { + border-top: 1px solid #eaecf0; + color: #54595d; + padding: 12px 16px 12px 44px; + font-size: 0.9em; + width: 100%; + margin-top: 1.6em; + line-height: 1.6; + position: absolute; + bottom: 0; + left: 0; +} diff --git a/vendor/assets/jquery.uls/css/jquery.uls.mobile.css b/vendor/assets/jquery.uls/css/jquery.uls.mobile.css new file mode 100644 index 0000000000..a7ae7d2be1 --- /dev/null +++ b/vendor/assets/jquery.uls/css/jquery.uls.mobile.css @@ -0,0 +1,307 @@ +/* stylelint-disable declaration-no-important */ +@media only screen and ( max-width: 767px ) { + .uls-mobile.uls-menu { + width: 95%; + left: 2.5%; + } + + .uls-mobile .uls-language-list { + -webkit-overflow-scrolling: touch; + } + + .uls-mobile .uls-language-block { + padding-left: 15px !important; + } + + .uls-mobile .uls-language-block ul { + min-height: 14em; + } + + .uls-mobile .uls-language-block a { + font-size: 1.143em; + line-height: 1.7em; + } + + .uls-mobile .row { + width: auto; + min-width: 0; + margin-left: 0; + margin-right: 0; + } + + .uls-mobile .column, + .uls-mobile .columns { + width: auto !important; + float: none; + } + + .uls-mobile .column:last-child, + .uls-mobile .columns:last-child { + float: none; + } + + .uls-mobile [ class*='column' ] + [ class*='column' ]:last-child { + float: none; + } + + .uls-mobile .column:before, + .uls-mobile .uls-mobile .columns:before, + .uls-mobile .column:after, + .columns:after { + content: ''; + display: table; + } + + .uls-mobile .column:after, + .uls-mobile .columns:after { + clear: both; + } + + .uls-mobile .offset-by-one, + .uls-mobile .offset-by-two, + .uls-mobile .offset-by-three, + .uls-mobile .offset-by-four, + .uls-mobile .offset-by-five, + .uls-mobile .offset-by-six, + .uls-mobile .offset-by-seven, + .uls-mobile .offset-by-eight, + .uls-mobile .offset-by-nine, + .uls-mobile .offset-by-ten { + margin-left: 0 !important; + } + + .uls-mobile .push-two, + .uls-mobile .push-three, + .uls-mobile .push-four, + .uls-mobile .push-five, + .uls-mobile .push-six, + .uls-mobile .push-seven, + .uls-mobile .push-eight, + .uls-mobile .push-nine, + .uls-mobile .push-ten { + left: auto; + } + + .uls-mobile .pull-two, + .uls-mobile .pull-three, + .uls-mobile .pull-four, + .uls-mobile .pull-five, + .uls-mobile .pull-six, + .uls-mobile .pull-seven, + .uls-mobile .pull-eight, + .uls-mobile .pull-nine, + .uls-mobile .pull-ten { + right: auto; + } + + /* Mobile 4-column Grid */ + .uls-mobile .row .mobile-one { + width: 25% !important; + float: left; + padding: 0 4px; + } + + .uls-mobile .row .mobile-one:last-child { + float: right; + } + + .uls-mobile .row.collapse .mobile-one { + padding: 0; + } + + .uls-mobile .row .mobile-two { + width: 50% !important; + float: left; + padding: 0 4px; + } + + .uls-mobile .row .mobile-two:last-child { + float: right; + } + + .uls-mobile .row.collapse .mobile-two { + padding: 0; + } + + .uls-mobile .row .mobile-three { + width: 75% !important; + float: left; + padding: 0 4px; + } + + .uls-mobile .row .mobile-three:last-child { + float: right; + } + + .uls-mobile .row.collapse .mobile-three { + padding: 0; + } + + .uls-mobile .row .mobile-four { + width: 100% !important; + float: left; + padding: 0 4px; + } + + .uls-mobile .row .mobile-four:last-child { + float: right; + } + + .uls-mobile .row.collapse .mobile-four { + padding: 0; + } + + .uls-mobile .push-one-mobile { + left: 25%; + } + + .uls-mobile .pull-one-mobile { + right: 25%; + } + + .uls-mobile .push-two-mobile { + left: 50%; + } + + .uls-mobile .pull-two-mobile { + right: 50%; + } + + .uls-mobile .push-three-mobile { + left: 75%; + } + + .uls-mobile .pull-three-mobile { + right: 75%; + } +} + +/* Visibility Classes ---------------------- */ +/* Standard (large) display targeting */ +.uls-mobile .show-for-small, +.uls-mobile .show-for-medium, +.uls-mobile .show-for-medium-down, +.uls-mobile .hide-for-large, +.uls-mobile .hide-for-large-up, +.uls-mobile .show-for-xlarge { + display: none !important; +} + +.uls-mobile .hide-for-xlarge, +.uls-mobile .show-for-large, +.uls-mobile .show-for-large-up, +.uls-mobile .hide-for-small, +.uls-mobile .hide-for-medium, +.uls-mobile .hide-for-medium-down { + display: block !important; +} + +/* Very large display targeting */ +@media only screen and ( min-width: 1441px ) { + .uls-mobile .hide-for-small, + .uls-mobile .hide-for-medium, + .uls-mobile .hide-for-medium-down, + .hide-for-large, + .show-for-large-up, + .show-for-xlarge { + display: block !important; + } + + .show-for-small, + .uls-mobile .show-for-medium, + .uls-mobile .show-for-medium-down, + .uls-mobile .show-for-large, + .uls-mobile .hide-for-large-up, + .uls-mobile .hide-for-xlarge { + display: none !important; + } +} + +/* Medium display targeting */ +@media only screen and ( max-width: 1279px ) and ( min-width: 768px ) { + .uls-mobile .hide-for-small, + .uls-mobile .show-for-medium, + .uls-mobile .show-for-medium-down, + .uls-mobile .hide-for-large, + .uls-mobile .hide-for-large-up, + .uls-mobile .hide-for-xlarge { + display: block !important; + } + + .uls-mobile .show-for-small, + .uls-mobile .hide-for-medium, + .uls-mobile .hide-for-medium-down, + .uls-mobile .show-for-large, + .uls-mobile .show-for-large-up, + .uls-mobile .show-for-xlarge { + display: none !important; + } +} + +/* Small display targeting */ +@media only screen and ( max-width: 767px ) { + .uls-mobile .show-for-small, + .uls-mobile .hide-for-medium, + .uls-mobile .show-for-medium-down, + .uls-mobile .hide-for-large, + .uls-mobile .hide-for-large-up, + .uls-mobile .hide-for-xlarge { + display: block !important; + } + + .uls-mobile .hide-for-small, + .uls-mobile .show-for-medium, + .uls-mobile .hide-for-medium-down, + .uls-mobile .show-for-large, + .uls-mobile .show-for-large-up, + .uls-mobile .show-for-xlarge { + display: none !important; + } +} + +/* Orientation targeting */ +.uls-mobile .show-for-landscape, +.uls-mobile .hide-for-portrait { + display: block !important; +} + +.uls-mobile .hide-for-landscape, +.uls-mobile .show-for-portrait { + display: none !important; +} + +@media screen and ( orientation: landscape ) { + .uls-mobile .show-for-landscape, + .uls-mobile .hide-for-portrait { + display: block !important; + } + + .uls-mobile .hide-for-landscape, + .uls-mobile .show-for-portrait { + display: none !important; + } +} + +@media screen and ( orientation: portrait ) { + .uls-mobile .show-for-portrait, + .uls-mobile .hide-for-landscape { + display: block !important; + } + + .uls-mobile .hide-for-portrait, + .uls-mobile .show-for-landscape { + display: none !important; + } +} + +/* Touch-enabled device targeting */ +.uls-mobile .show-for-touch, +.uls-mobile .touch .hide-for-touch { + display: none !important; +} + +/* stylelint-disable-next-line no-descending-specificity */ +.uls-mobile .hide-for-touch, +.uls-mobile .touch .show-for-touch { + display: block !important; +} diff --git a/vendor/assets/jquery.uls/images/close.svg b/vendor/assets/jquery.uls/images/close.svg new file mode 100644 index 0000000000..4ed25ba2e6 --- /dev/null +++ b/vendor/assets/jquery.uls/images/close.svg @@ -0,0 +1,8 @@ + + + + close + + + + diff --git a/vendor/assets/jquery.uls/images/language.svg b/vendor/assets/jquery.uls/images/language.svg new file mode 100644 index 0000000000..9262f73faa --- /dev/null +++ b/vendor/assets/jquery.uls/images/language.svg @@ -0,0 +1,7 @@ + + + + language + + + diff --git a/vendor/assets/jquery.uls/images/search.svg b/vendor/assets/jquery.uls/images/search.svg new file mode 100644 index 0000000000..61be6a402e --- /dev/null +++ b/vendor/assets/jquery.uls/images/search.svg @@ -0,0 +1,7 @@ + + + + search + + + diff --git a/vendor/assets/jquery.uls/src/jquery.uls.core.js b/vendor/assets/jquery.uls/src/jquery.uls.core.js new file mode 100644 index 0000000000..1c5d2d1700 --- /dev/null +++ b/vendor/assets/jquery.uls/src/jquery.uls.core.js @@ -0,0 +1,499 @@ +/** + * Universal Language Selector + * ULS core component. + * + * Copyright (C) 2012 Alolita Sharma, Amir Aharoni, Arun Ganesh, Brandon Harris, + * Niklas Laxström, Pau Giner, Santhosh Thottingal, Siebrand Mazeland and other + * contributors. See CREDITS for a list. + * + * UniversalLanguageSelector is dual licensed GPLv2 or later and MIT. You don't + * have to do anything special to choose one license or the other and you don't + * have to notify anyone which license you are using. You are free to use + * UniversalLanguageSelector in commercial projects as long as the copyright + * header is left intact. See files GPL-LICENSE and MIT-LICENSE for details. + * + * @file + * @license GNU General Public Licence 2.0 or later + * @license MIT License + */ + +( function ( $ ) { + 'use strict'; + + var template, ULS; + + // Region numbers in id attributes also appear in the langdb. + template = '
    \ + \ +
    \ +
    \ +
    '; + + /** + * ULS Public class definition + * + * @param {Element} element + * @param {Object} options + */ + ULS = function ( element, options ) { + var code; + this.$element = $( element ); + this.options = $.extend( {}, $.fn.uls.defaults, options ); + this.$menu = $( template ); + this.languages = this.options.languages; + + for ( code in this.languages ) { + if ( $.uls.data.languages[ code ] === undefined ) { + // Language is unknown to ULS. + delete this.languages[ code ]; + } + } + + this.left = this.options.left; + this.top = this.options.top; + this.shown = false; + this.initialized = false; + this.shouldRecreate = false; + this.menuWidth = this.getMenuWidth(); + + this.$languageFilter = this.$menu.find( '.uls-languagefilter' ); + this.$resultsView = this.$menu.find( '.uls-language-list' ); + + this.render(); + this.listen(); + this.ready(); + }; + + ULS.prototype = { + constructor: ULS, + + /** + * A "hook" that runs after the ULS constructor. + * At this point it is not guaranteed that the ULS has its dimensions + * and that the languages lists are initialized. + * + * To use it, pass a function as the onReady parameter + * in the options when initializing ULS. + */ + ready: function () { + if ( this.options.onReady ) { + this.options.onReady.call( this ); + } + }, + + /** + * A "hook" that runs after the ULS panel becomes visible + * by using the show method. + * + * To use it, pass a function as the onVisible parameter + * in the options when initializing ULS. + */ + visible: function () { + if ( this.options.onVisible ) { + this.options.onVisible.call( this ); + } + }, + + /** + * Calculate the position of ULS + * Returns an object with top and left properties. + * + * @return {Object} + */ + position: function () { + var pos, + top = this.top, + left = this.left; + + if ( this.options.onPosition ) { + return this.options.onPosition.call( this ); + } + + // Default implementation (middle of the screen under the trigger) + if ( top === undefined ) { + pos = $.extend( {}, this.$element.offset(), { + height: this.$element[ 0 ].offsetHeight + } ); + top = pos.top + pos.height; + } + + if ( left === undefined ) { + left = $( window ).width() / 2 - this.$menu.outerWidth() / 2; + } + + return { + top: top, + left: left + }; + }, + + /** + * Show the ULS window + */ + show: function () { + var widthClasses = { + wide: 'uls-wide', + medium: 'uls-medium', + narrow: 'uls-narrow' + }; + + this.$menu.addClass( widthClasses[ this.menuWidth ] ); + + if ( !this.initialized ) { + $( document.body ).prepend( this.$menu ); + this.i18n(); + this.initialized = true; + } + + this.$menu.css( this.position() ); + this.$menu.show(); + this.$menu.scrollIntoView(); + this.shown = true; + + if ( !this.isMobile() ) { + this.$languageFilter.trigger( 'focus' ); + } + + this.visible(); + }, + + i18n: function () { + if ( $.i18n ) { + this.$menu.find( '[data-i18n]' ).i18n(); + this.$languageFilter.prop( 'placeholder', $.i18n( 'uls-search-placeholder' ) ); + } + }, + + /** + * Hide the ULS window + */ + hide: function () { + this.$menu.hide(); + this.shown = false; + + this.$menu.removeClass( 'uls-wide uls-medium uls-narrow' ); + + if ( this.shouldRecreate ) { + this.recreateLanguageFilter(); + } + + if ( this.options.onCancel ) { + this.options.onCancel.call( this ); + } + }, + + /** + * Render the UI elements. + * Does nothing by default. Can be used for customization. + */ + render: function () { + // Rendering stuff here + }, + + /** + * Callback for results found context. + */ + success: function () { + this.$resultsView.show(); + }, + + createLanguageFilter: function () { + var lcd, languagesCount, + columnsOptions = { + wide: 4, + medium: 2, + narrow: 1 + }; + + languagesCount = Object.keys( this.options.languages ).length; + lcd = this.$resultsView.lcd( { + languages: this.languages, + columns: columnsOptions[ this.menuWidth ], + + quickList: languagesCount > 12 ? this.options.quickList : [], + clickhandler: this.select.bind( this ), + showRegions: this.options.showRegions, + languageDecorator: this.options.languageDecorator, + noResultsTemplate: this.options.noResultsTemplate, + itemsPerColumn: this.options.itemsPerColumn, + groupByRegion: this.options.groupByRegion + } ).data( 'lcd' ); + + this.$languageFilter.languagefilter( { + lcd: lcd, + languages: this.languages, + ulsPurpose: this.options.ulsPurpose, + searchAPI: this.options.searchAPI, + onSelect: this.select.bind( this ) + } ); + + this.$languageFilter.on( 'noresults.uls', lcd.noResults.bind( lcd ) ); + }, + + recreateLanguageFilter: function () { + this.$resultsView.removeData( 'lcd' ); + this.$resultsView.empty(); + this.$languageFilter.removeData( 'languagefilter' ); + this.createLanguageFilter(); + + this.shouldRecreate = false; + }, + + /** + * Bind the UI elements with their event listeners + */ + listen: function () { + // Register all event listeners to the ULS here. + this.$element.on( 'click', this.click.bind( this ) ); + + // Don't do anything if pressing on empty space in the ULS + this.$menu.on( 'click', function ( e ) { + e.stopPropagation(); + } ); + + // Handle key press events on the menu + this.$menu.on( 'keydown', this.keypress.bind( this ) ); + + this.createLanguageFilter(); + + this.$languageFilter.on( 'resultsfound.uls', this.success.bind( this ) ); + + $( document.body ).on( 'click', this.cancel.bind( this ) ); + $( window ).on( 'resize', $.fn.uls.debounce( this.resize.bind( this ), 250 ) ); + }, + + resize: function () { + var menuWidth = this.getMenuWidth(); + + if ( this.menuWidth === menuWidth ) { + return; + } + + this.menuWidth = menuWidth; + this.shouldRecreate = true; + if ( !this.shown ) { + this.recreateLanguageFilter(); + } + }, + + /** + * On select handler for search results + * + * @param {string} langCode + * @param {Object} event The jQuery click event + */ + select: function ( langCode, event ) { + this.hide(); + if ( this.options.onSelect ) { + this.options.onSelect.call( this, langCode, event ); + } + }, + + /** + * On cancel handler for the uls menu + * + * @param {Event} e + */ + cancel: function ( e ) { + if ( e && ( this.$element.is( e.target ) || + $.contains( this.$element[ 0 ], e.target ) ) ) { + return; + } + + this.hide(); + }, + + keypress: function ( e ) { + if ( !this.shown ) { + return; + } + + if ( e.keyCode === 27 ) { // escape + this.cancel(); + e.preventDefault(); + e.stopPropagation(); + } + }, + + click: function () { + if ( this.shown ) { + this.hide(); + } else { + this.show(); + } + }, + + /** + * Get the panel menu width parameter + * + * @return {string} + */ + getMenuWidth: function () { + var languagesCount, + screenWidth = document.documentElement.clientWidth; + + if ( this.options.menuWidth ) { + return this.options.menuWidth; + } + + languagesCount = Object.keys( this.options.languages ).length; + + if ( screenWidth > 900 && languagesCount >= 48 ) { + return 'wide'; + } + + if ( screenWidth > 500 && languagesCount >= 24 ) { + return 'medium'; + } + + return 'narrow'; + }, + + isMobile: function () { + return navigator.userAgent.match( /(iPhone|iPod|iPad|Android|BlackBerry)/ ); + } + }; + + /* ULS PLUGIN DEFINITION + * =========================== */ + + $.fn.uls = function ( option ) { + return this.each( function () { + var $this = $( this ), + data = $this.data( 'uls' ), + options = typeof option === 'object' && option; + + if ( !data ) { + $this.data( 'uls', ( data = new ULS( this, options ) ) ); + } + + if ( typeof option === 'string' ) { + data[ option ](); + } + } ); + }; + + $.fn.uls.defaults = { + // DEPRECATED: CSS top position for the dialog + top: undefined, + // DEPRECATED: CSS left position for the dialog + left: undefined, + // Callback function when user selects a language + onSelect: undefined, + // Callback function when the dialog is closed without selecting a language + onCancel: undefined, + // Callback function when ULS has initialized + onReady: undefined, + // Callback function when ULS dialog is shown + onVisible: undefined, + // Callback function when ULS dialog is ready to be shown + onPosition: undefined, + // Languages to be used for ULS, default is all languages + languages: $.uls.data.getAutonyms(), + // The options are wide (4 columns), medium (2 columns), and narrow (1 column). + // If not specified, it will be set automatically. + menuWidth: undefined, + // What is this ULS used for. + // Should be set for distinguishing between different instances of ULS + // in the same application. + ulsPurpose: '', + // Used by LCD + quickList: [], + // Used by LCD + showRegions: undefined, + // Used by LCD + languageDecorator: undefined, + // Used by LCD + noResultsTemplate: undefined, + // Used by LCD + itemsPerColumn: undefined, + // Used by LCD + groupByRegion: undefined, + // Used by LanguageFilter + searchAPI: undefined + }; + + // Define a dummy i18n function, if jquery.i18n not integrated. + if ( !$.fn.i18n ) { + $.fn.i18n = function () {}; + } + + /** + * Creates and returns a new debounced version of the passed function, + * which will postpone its execution, until after wait milliseconds have elapsed + * since the last time it was invoked. + * + * @param {Function} fn Function to be debounced. + * @param {number} wait Wait interval in milliseconds. + * @param {boolean} [immediate] Trigger the function on the leading edge of the wait interval, + * instead of the trailing edge. + * @return {Function} Debounced function. + */ + $.fn.uls.debounce = function ( fn, wait, immediate ) { + var timeout; + + return function () { + var callNow, self = this, + later = function () { + timeout = null; + if ( !immediate ) { + fn.apply( self, arguments ); + } + }; + + callNow = immediate && !timeout; + clearTimeout( timeout ); + timeout = setTimeout( later, wait || 100 ); + + if ( callNow ) { + fn.apply( self, arguments ); + } + }; + }; + + /* + * Simple scrollIntoView plugin. + * Scrolls the element to the viewport smoothly if it is not already. + */ + $.fn.scrollIntoView = function () { + return this.each( function () { + var scrollPosition, + $window = $( window ), + windowHeight = $window.height(), + windowTop = $window.scrollTop(), + windowBottom = windowTop + windowHeight, + $element = $( this ), + panelHeight = $element.height(), + panelTop = $element.offset().top, + panelBottom = panelTop + panelHeight; + + if ( ( panelTop < windowTop ) || ( panelBottom > windowBottom ) ) { + if ( windowTop > panelTop ) { + scrollPosition = panelTop; + } else { + scrollPosition = panelBottom - windowHeight; + } + // eslint-disable-next-line no-jquery/no-global-selector + $( 'html, body' ).stop().animate( { + scrollTop: scrollPosition + }, 500 ); + } + } ); + }; + + $.fn.uls.Constructor = ULS; +}( jQuery ) ); diff --git a/vendor/assets/jquery.uls/src/jquery.uls.data.js b/vendor/assets/jquery.uls/src/jquery.uls.data.js new file mode 100644 index 0000000000..ec644b9a65 --- /dev/null +++ b/vendor/assets/jquery.uls/src/jquery.uls.data.js @@ -0,0 +1,6412 @@ +( function () { function r( e, n, t ) { function o( i, f ) { if ( !n[ i ] ) { if ( !e[ i ] ) { var c = typeof require === 'function' && require; if ( !f && c ) { return c( i, !0 ); } if ( u ) { return u( i, !0 ); } var a = new Error( "Cannot find module '" + i + "'" ); throw a.code = 'MODULE_NOT_FOUND', a; } var p = n[ i ] = { exports: {} }; e[ i ][ 0 ].call( p.exports, function ( r ) { var n = e[ i ][ 1 ][ r ]; return o( n || r ); }, p, p.exports, r, e, n, t ); } return n[ i ].exports; } for ( var u = typeof require === 'function' && require, i = 0; i < t.length; i++ ) { o( t[ i ] ); } return o; } return r; }() )( { + 1: [ function ( require, module, exports ) { + module.exports = ( function ( $ ) { + $.uls = $.uls || {}; + $.uls.data = +{ + "languages": { + "aa": [ + "Latn", + [ + "AF" + ], + "Qafár af" + ], + "ab": [ + "Cyrl", + [ + "EU" + ], + "аԥсшәа" + ], + "abe": [ + "Latn", + [ + "AM" + ], + "Wôbanakiôdwawôgan" + ], + "abs": [ + "Latn", + [ + "AS" + ], + "Bahasa Ambon" + ], + "ace": [ + "Latn", + [ + "AS", + "PA" + ], + "Acèh" + ], + "acf": [ + "Latn", + [ + "AM" + ], + "kwéyòl" + ], + "ady": [ + "Cyrl", + [ + "EU", + "ME" + ], + "адыгабзэ" + ], + "ady-cyrl": [ + "ady" + ], + "ady-latn": [ + "Latn", + [ + "EU", + "ME" + ], + "Adygabze" + ], + "aeb": [ + "aeb-arab" + ], + "aeb-arab": [ + "Arab", + [ + "AF" + ], + "تونسي" + ], + "aeb-latn": [ + "Latn", + [ + "AF" + ], + "Tûnsî" + ], + "af": [ + "Latn", + [ + "AF" + ], + "Afrikaans" + ], + "agq": [ + "Latn", + [ + "AF" + ], + "aghɨ̂m" + ], + "ahr": [ + "Deva", + [ + "AS" + ], + "अहिराणी" + ], + "ajg": [ + "Latn", + [ + "AF" + ], + "ajagbe" + ], + "ajp": [ + "Arab", + [ + "ME" + ], + "شامي" + ], + "ajp-arab": [ + "ajp" + ], + "ajp-latn": [ + "Latn", + [ + "ME" + ], + "šāmi" + ], + "ak": [ + "Latn", + [ + "AF" + ], + "Akan" + ], + "akz": [ + "Latn", + [ + "AM" + ], + "Albaamo innaaɬiilka" + ], + "ale": [ + "Latn", + [ + "AM", + "AS" + ], + "unangam tunuu" + ], + "ale-cyrl": [ + "Cyrl", + [ + "AS" + ], + "унаӈам тунуу" + ], + "aln": [ + "Latn", + [ + "EU" + ], + "Gegë" + ], + "alt": [ + "Cyrl", + [ + "EU", + "AS" + ], + "алтай тил" + ], + "am": [ + "Ethi", + [ + "AF" + ], + "አማርኛ" + ], + "ami": [ + "Latn", + [ + "AS" + ], + "Pangcah" + ], + "an": [ + "Latn", + [ + "EU" + ], + "aragonés" + ], + "ang": [ + "Latn", + [ + "EU" + ], + "Ænglisc" + ], + "ann": [ + "Latn", + [ + "AF" + ], + "Obolo" + ], + "anp": [ + "Deva", + [ + "AS" + ], + "अंगिका" + ], + "ar": [ + "Arab", + [ + "AF", + "ME" + ], + "العربية" + ], + "arc": [ + "Syrc", + [ + "ME" + ], + "ܐܪܡܝܐ" + ], + "arn": [ + "Latn", + [ + "AM" + ], + "mapudungun" + ], + "aro": [ + "Latn", + [ + "AM" + ], + "Araona" + ], + "arq": [ + "Arab", + [ + "AF", + "ME" + ], + "جازايرية" + ], + "ary": [ + "Arab", + [ + "AF", + "ME" + ], + "الدارجة" + ], + "ary-arab": [ + "ary" + ], + "ary-latn": [ + "Latn", + [ + "AF", + "ME" + ], + "ed-dārija" + ], + "arz": [ + "Arab", + [ + "AF", + "ME" + ], + "مصرى" + ], + "as": [ + "Beng", + [ + "AS" + ], + "অসমীয়া" + ], + "ase": [ + "Sgnw", + [ + "AM" + ], + "American sign language" + ], + "ast": [ + "Latn", + [ + "EU" + ], + "asturianu" + ], + "atj": [ + "Latn", + [ + "AM" + ], + "atikamekw" + ], + "atv": [ + "Cyrl", + [ + "EU", + "AS" + ], + "тÿндÿк алтай тил" + ], + "av": [ + "Cyrl", + [ + "EU" + ], + "авар" + ], + "avk": [ + "Latn", + [ + "WW" + ], + "Kotava" + ], + "awa": [ + "Deva", + [ + "AS" + ], + "अवधी" + ], + "ay": [ + "Latn", + [ + "AM" + ], + "Aymar aru" + ], + "az": [ + "az-latn" + ], + "az-arab": [ + "Arab", + [ + "AS", + "ME" + ], + "تۆرکجه" + ], + "az-latn": [ + "Latn", + [ + "EU", + "ME" + ], + "azərbaycanca" + ], + "az-cyrl": [ + "Cyrl", + [ + "EU", + "ME" + ], + "азәрбајҹанҹа" + ], + "azb": [ + "az-arab" + ], + "azj": [ + "az-latn" + ], + "ba": [ + "Cyrl", + [ + "EU" + ], + "башҡортса" + ], + "ban": [ + "Latn", + [ + "AS", + "PA" + ], + "Bali" + ], + "ban-bali": [ + "Bali", + [ + "AS", + "PA" + ], + "ᬩᬮᬶ" + ], + "bar": [ + "Latn", + [ + "EU" + ], + "Boarisch" + ], + "bas": [ + "Latn", + [ + "AF" + ], + "ɓasaá" + ], + "bat-smg": [ + "sgs" + ], + "bbc-latn": [ + "Latn", + [ + "AS" + ], + "Batak Toba" + ], + "bbc-batk": [ + "Batk", + [ + "AS" + ], + "ᯅᯖᯂ᯲ ᯖᯬᯅ" + ], + "bbc": [ + "bbc-latn" + ], + "bcc": [ + "Arab", + [ + "AS", + "ME" + ], + "جهلسری بلوچی" + ], + "bci": [ + "Latn", + [ + "AF" + ], + "wawle" + ], + "bcl": [ + "Latn", + [ + "AS" + ], + "Bikol Central" + ], + "bdr": [ + "Latn", + [ + "AS" + ], + "Bajau Sama" + ], + "be-tarask": [ + "Cyrl", + [ + "EU" + ], + "беларуская (тарашкевіца)" + ], + "be-x-old": [ + "be-tarask" + ], + "be": [ + "Cyrl", + [ + "EU" + ], + "беларуская" + ], + "bew": [ + "Latn", + [ + "AS" + ], + "Bahasa Betawi" + ], + "bfa": [ + "Latn", + [ + "AF" + ], + "Bari" + ], + "bft": [ + "Arab", + [ + "AS" + ], + "بلتی" + ], + "bfq": [ + "Taml", + [ + "AS" + ], + "படகா" + ], + "bg": [ + "Cyrl", + [ + "EU" + ], + "български" + ], + "bgn": [ + "Arab", + [ + "AS", + "ME" + ], + "روچ کپتین بلوچی" + ], + "bh": [ + "bho" + ], + "bho": [ + "Deva", + [ + "AS" + ], + "भोजपुरी" + ], + "bi": [ + "Latn", + [ + "PA" + ], + "Bislama" + ], + "bjn": [ + "Latn", + [ + "AS" + ], + "Banjar" + ], + "bkm": [ + "Latn", + [ + "AF" + ], + "Itaŋikom" + ], + "blc": [ + "Latn", + [ + "AM" + ], + "ItNuxalkmc" + ], + "blk": [ + "Mymr", + [ + "AS" + ], + "ပအိုဝ်ႏဘာႏသာႏ" + ], + "bm": [ + "Latn", + [ + "AF" + ], + "bamanankan" + ], + "bn": [ + "Beng", + [ + "AS" + ], + "বাংলা" + ], + "bnn": [ + "Latn", + [ + "AS" + ], + "bunun" + ], + "bo": [ + "Tibt", + [ + "AS" + ], + "བོད་ཡིག" + ], + "bpy": [ + "Beng", + [ + "AS" + ], + "বিষ্ণুপ্রিয়া মণিপুরী" + ], + "bqi": [ + "Arab", + [ + "AS", + "ME" + ], + "بختیاری" + ], + "br": [ + "Latn", + [ + "EU" + ], + "brezhoneg" + ], + "brh": [ + "Latn", + [ + "ME", + "AS" + ], + "Bráhuí" + ], + "brx": [ + "Deva", + [ + "AS" + ], + "बर'" + ], + "bs": [ + "Latn", + [ + "EU" + ], + "bosanski" + ], + "btm": [ + "Latn", + [ + "AS" + ], + "Mandailing" + ], + "bto": [ + "Latn", + [ + "AS" + ], + "Iriga Bicolano" + ], + "bug": [ + "Bugi", + [ + "AS" + ], + "ᨅᨔ ᨕᨘᨁᨗ" + ], + "bum": [ + "Latn", + [ + "AF" + ], + "bulu" + ], + "bwr": [ + "Latn", + [ + "AF" + ], + "bura" + ], + "bxr": [ + "Cyrl", + [ + "AS" + ], + "буряад" + ], + "byn": [ + "Ethi", + [ + "AF" + ], + "ብሊን" + ], + "bzj": [ + "Latn", + [ + "AM" + ], + "Bileez Kriol" + ], + "ca": [ + "Latn", + [ + "EU" + ], + "català" + ], + "cak": [ + "Latn", + [ + "AM" + ], + "Kaqchikel" + ], + "cbk": [ + "Latn", + [ + "AS" + ], + "Chavacano de Zamboanga" + ], + "cbk-zam": [ + "cbk" + ], + "ccp": [ + "Cakm", + [ + "AS" + ], + "𑄌𑄋𑄴𑄟𑄳𑄦" + ], + "cdo": [ + "Latn", + [ + "AS" + ], + "Mìng-dĕ̤ng-ngṳ̄" + ], + "cdo-latn": [ + "Latn", + [ + "AS" + ], + "Mìng-dĕ̤ng-ngṳ̄ Bàng-uâ-cê" + ], + "cdo-hani": [ + "Hani", + [ + "AS" + ], + "閩東語(漢字)" + ], + "ce": [ + "Cyrl", + [ + "EU" + ], + "нохчийн" + ], + "ceb": [ + "Latn", + [ + "AS" + ], + "Cebuano" + ], + "ch": [ + "Latn", + [ + "PA" + ], + "Chamoru" + ], + "chm": [ + "mhr" + ], + "chn": [ + "Latn", + [ + "AM" + ], + "chinuk wawa" + ], + "cho": [ + "Latn", + [ + "AM" + ], + "Choctaw" + ], + "chr": [ + "Cher", + [ + "AM" + ], + "ᏣᎳᎩ" + ], + "chy": [ + "Latn", + [ + "AM" + ], + "Tsetsêhestâhese" + ], + "ciw": [ + "Latn", + [ + "AM" + ], + "Anishinaabemowin" + ], + "cjy": [ + "cjy-hant" + ], + "cjy-hans": [ + "Hans", + [ + "AS" + ], + "晋语(简化字)" + ], + "cjy-hant": [ + "Hant", + [ + "AS" + ], + "晉語" + ], + "ckb": [ + "Arab", + [ + "ME" + ], + "کوردی" + ], + "ckt": [ + "Cyrl", + [ + "AS" + ], + "ԓыгъоравэтԓьэн" + ], + "cnh": [ + "Latn", + [ + "AS" + ], + "Lai holh" + ], + "cnr": [ + "cnr-latn" + ], + "cnr-cyrl": [ + "Cyrl", + [ + "EU" + ], + "црногорски" + ], + "cnr-latn": [ + "Latn", + [ + "EU" + ], + "crnogorski" + ], + "co": [ + "Latn", + [ + "EU" + ], + "corsu" + ], + "cop": [ + "Copt", + [ + "AF", + "ME" + ], + "ϯⲙⲉⲧⲣⲉⲙⲛ̀ⲭⲏⲙⲓ" + ], + "cps": [ + "Latn", + [ + "AS" + ], + "Capiceño" + ], + "cr": [ + "Cans", + [ + "AM" + ], + "ᓀᐦᐃᔭᐍᐏᐣ" + ], + "cr-cans": [ + "cr" + ], + "cr-latn": [ + "Latn", + [ + "AM" + ], + "Nēhiyawēwin" + ], + "crh": [ + "Latn", + [ + "EU" + ], + "qırımtatarca" + ], + "crh-cyrl": [ + "Cyrl", + [ + "EU" + ], + "къырымтатарджа" + ], + "crh-latn": [ + "crh" + ], + "cs": [ + "Latn", + [ + "EU" + ], + "čeština" + ], + "csb": [ + "Latn", + [ + "EU" + ], + "kaszëbsczi" + ], + "cu": [ + "Cyrl", + [ + "EU" + ], + "словѣньскъ \/ ⰔⰎⰑⰂⰡⰐⰠⰔⰍⰟ" + ], + "cv": [ + "Cyrl", + [ + "EU" + ], + "чӑвашла" + ], + "cy": [ + "Latn", + [ + "EU" + ], + "Cymraeg" + ], + "da": [ + "Latn", + [ + "EU" + ], + "dansk" + ], + "dag": [ + "Latn", + [ + "AF" + ], + "dagbanli" + ], + "dar": [ + "Cyrl", + [ + "EU" + ], + "дарган" + ], + "de-at": [ + "Latn", + [ + "EU" + ], + "Österreichisches Deutsch" + ], + "de-ch": [ + "Latn", + [ + "EU" + ], + "Schweizer Hochdeutsch" + ], + "de-formal": [ + "Latn", + [ + "EU" + ], + "Deutsch (Sie-Form)" + ], + "de": [ + "Latn", + [ + "EU" + ], + "Deutsch" + ], + "din": [ + "Latn", + [ + "AF" + ], + "Thuɔŋjäŋ" + ], + "diq": [ + "Latn", + [ + "EU", + "AS" + ], + "Zazaki" + ], + "doi": [ + "Deva", + [ + "AS" + ], + "डोगरी" + ], + "dsb": [ + "Latn", + [ + "EU" + ], + "dolnoserbski" + ], + "dtp": [ + "Latn", + [ + "AS" + ], + "Dusun Bundu-liwan" + ], + "dty": [ + "Deva", + [ + "AS" + ], + "डोटेली" + ], + "dv": [ + "Thaa", + [ + "AS" + ], + "ދިވެހިބަސް" + ], + "dz": [ + "Tibt", + [ + "AS" + ], + "ཇོང་ཁ" + ], + "ee": [ + "Latn", + [ + "AF" + ], + "eʋegbe" + ], + "efi": [ + "Latn", + [ + "AF" + ], + "efịk" + ], + "egl": [ + "Latn", + [ + "EU" + ], + "Emiliàn" + ], + "el": [ + "Grek", + [ + "EU" + ], + "Ελληνικά" + ], + "elm": [ + "Latn", + [ + "AF" + ], + "Eleme" + ], + "eml": [ + "Latn", + [ + "EU" + ], + "emiliàn e rumagnòl" + ], + "en-ca": [ + "Latn", + [ + "AM" + ], + "Canadian English" + ], + "en-gb": [ + "Latn", + [ + "EU", + "AS", + "PA" + ], + "British English" + ], + "en-simple": [ + "Latn", + [ + "WW" + ], + "Simple English" + ], + "en": [ + "Latn", + [ + "EU", + "AM", + "AF", + "ME", + "AS", + "PA", + "WW" + ], + "English" + ], + "eo": [ + "Latn", + [ + "WW" + ], + "Esperanto" + ], + "es-419": [ + "Latn", + [ + "AM" + ], + "español de América Latina" + ], + "es-formal": [ + "Latn", + [ + "EU", + "AM", + "AF", + "WW" + ], + "español (formal)" + ], + "es": [ + "Latn", + [ + "EU", + "AM", + "AF", + "WW", + "PA" + ], + "español" + ], + "es-ni": [ + "Latn", + [ + "AM" + ], + "español nicaragüense" + ], + "esu": [ + "Latn", + [ + "AM" + ], + "Yup'ik" + ], + "et": [ + "Latn", + [ + "EU" + ], + "eesti" + ], + "eu": [ + "Latn", + [ + "EU" + ], + "euskara" + ], + "ext": [ + "Latn", + [ + "EU" + ], + "estremeñu" + ], + "eya": [ + "Latn", + [ + "AM" + ], + "I·ya·q" + ], + "fa": [ + "Arab", + [ + "AS", + "ME" + ], + "فارسی" + ], + "fan": [ + "Latn", + [ + "AF" + ], + "Faŋ" + ], + "fat": [ + "Latn", + [ + "AF" + ], + "mfantse" + ], + "fax": [ + "Latn", + [ + "EU" + ], + "Fala" + ], + "ff": [ + "Latn", + [ + "AF" + ], + "Fulfulde" + ], + "fi": [ + "Latn", + [ + "EU" + ], + "suomi" + ], + "fil": [ + "tl" + ], + "fit": [ + "Latn", + [ + "EU" + ], + "meänkieli" + ], + "fiu-vro": [ + "vro" + ], + "fj": [ + "Latn", + [ + "PA" + ], + "Na Vosa Vakaviti" + ], + "fkv": [ + "Latn", + [ + "EU" + ], + "kvääni" + ], + "fo": [ + "Latn", + [ + "EU" + ], + "føroyskt" + ], + "fon": [ + "Latn", + [ + "AF" + ], + "fɔ̀ngbè" + ], + "fr": [ + "Latn", + [ + "EU", + "AM", + "WW" + ], + "français" + ], + "frc": [ + "Latn", + [ + "AM" + ], + "français cadien" + ], + "frp": [ + "Latn", + [ + "EU" + ], + "arpetan" + ], + "frr": [ + "Latn", + [ + "EU" + ], + "Nordfriisk" + ], + "fuf": [ + "Latn", + [ + "AF" + ], + "Fuuta Jalon" + ], + "fur": [ + "Latn", + [ + "EU" + ], + "furlan" + ], + "fvr": [ + "Latn", + [ + "AF" + ], + "poor’íŋ belé’ŋ" + ], + "fy": [ + "Latn", + [ + "EU" + ], + "Frysk" + ], + "ga": [ + "Latn", + [ + "EU" + ], + "Gaeilge" + ], + "gaa": [ + "Latn", + [ + "AF" + ], + "Ga" + ], + "gag": [ + "Latn", + [ + "EU" + ], + "Gagauz" + ], + "gah": [ + "Latn", + [ + "AS" + ], + "Alekano" + ], + "gan-hans": [ + "Hans", + [ + "AS" + ], + "赣语(简体)" + ], + "gan-hant": [ + "gan" + ], + "gan": [ + "Hant", + [ + "AS" + ], + "贛語" + ], + "gbm": [ + "Deva", + [ + "AS" + ], + "गढ़वळि" + ], + "gbz": [ + "Latn", + [ + "AS" + ], + "Dari-e Mazdeyasnā" + ], + "gcf": [ + "Latn", + [ + "AM" + ], + "Guadeloupean Creole French" + ], + "gcr": [ + "Latn", + [ + "AM" + ], + "kriyòl gwiyannen" + ], + "gd": [ + "Latn", + [ + "EU" + ], + "Gàidhlig" + ], + "gez": [ + "Ethi", + [ + "AF" + ], + "ግዕዝ" + ], + "gl": [ + "Latn", + [ + "EU" + ], + "galego" + ], + "gld": [ + "Cyrl", + [ + "AS" + ], + "на̄ни" + ], + "glk": [ + "Arab", + [ + "AS", + "ME" + ], + "گیلکی" + ], + "gn": [ + "Latn", + [ + "AM" + ], + "Avañe'ẽ" + ], + "gom": [ + "gom-deva" + ], + "gom-deva": [ + "Deva", + [ + "AS" + ], + "गोंयची कोंकणी" + ], + "gom-latn": [ + "Latn", + [ + "AS" + ], + "Gõychi Konknni" + ], + "gor": [ + "Latn", + [ + "AS" + ], + "Bahasa Hulontalo" + ], + "got": [ + "Goth", + [ + "EU" + ], + "𐌲𐌿𐍄𐌹𐍃𐌺" + ], + "gpe": [ + "Latn", + [ + "AF" + ], + "Ghanaian Pidgin" + ], + "grc": [ + "Grek", + [ + "EU" + ], + "Ἀρχαία ἑλληνικὴ" + ], + "gsw": [ + "Latn", + [ + "EU" + ], + "Alemannisch" + ], + "gu": [ + "Gujr", + [ + "AS" + ], + "ગુજરાતી" + ], + "guc": [ + "Latn", + [ + "AM" + ], + "wayuunaiki" + ], + "gum": [ + "Latn", + [ + "AM" + ], + "Namtrik" + ], + "gur": [ + "Latn", + [ + "AF" + ], + "farefare" + ], + "guw": [ + "Latn", + [ + "AF" + ], + "gungbe" + ], + "gv": [ + "Latn", + [ + "EU" + ], + "Gaelg" + ], + "ha": [ + "Latn", + [ + "AF" + ], + "Hausa" + ], + "ha-arab": [ + "Arab", + [ + "AF" + ], + "هَوُسَ" + ], + "ha-latn": [ + "ha" + ], + "hai": [ + "Latn", + [ + "AM" + ], + "X̱aat Kíl" + ], + "hak": [ + "Latn", + [ + "AS" + ], + "Hak-kâ-fa" + ], + "haw": [ + "Latn", + [ + "AM", + "PA" + ], + "Hawai`i" + ], + "he": [ + "Hebr", + [ + "ME" + ], + "עברית" + ], + "hak-hans": [ + "Hans", + [ + "AS" + ], + "客家语(简体)" + ], + "hak-hant": [ + "Hant", + [ + "AS" + ], + "客家語(繁體)" + ], + "hi": [ + "Deva", + [ + "AS" + ], + "हिन्दी" + ], + "hif": [ + "Latn", + [ + "PA", + "AS" + ], + "Fiji Hindi" + ], + "hif-deva": [ + "Deva", + [ + "AS" + ], + "फ़ीजी हिन्दी" + ], + "hif-latn": [ + "hif" + ], + "hil": [ + "Latn", + [ + "AS" + ], + "Ilonggo" + ], + "hne": [ + "Deva", + [ + "AS" + ], + "छत्तीसगढ़ी" + ], + "ho": [ + "Latn", + [ + "PA" + ], + "Hiri Motu" + ], + "hoc": [ + "Wara", + [ + "AS" + ], + "𑢹𑣉𑣉" + ], + "hr": [ + "Latn", + [ + "EU" + ], + "hrvatski" + ], + "hrx": [ + "Latn", + [ + "AM" + ], + "Hunsrik" + ], + "hsb": [ + "Latn", + [ + "EU" + ], + "hornjoserbsce" + ], + "hsn": [ + "Hans", + [ + "AS" + ], + "湘语" + ], + "ht": [ + "Latn", + [ + "AM" + ], + "Kreyòl ayisyen" + ], + "hu-formal": [ + "Latn", + [ + "EU" + ], + "Magyar (magázó)" + ], + "hu": [ + "Latn", + [ + "EU" + ], + "magyar" + ], + "hy": [ + "Armn", + [ + "EU", + "ME" + ], + "հայերեն" + ], + "hyw": [ + "Armn", + [ + "EU", + "ME" + ], + "Արեւմտահայերէն" + ], + "hz": [ + "Latn", + [ + "AF" + ], + "Otsiherero" + ], + "ia": [ + "Latn", + [ + "WW" + ], + "interlingua" + ], + "id": [ + "Latn", + [ + "AS", + "PA" + ], + "Bahasa Indonesia" + ], + "ie": [ + "Latn", + [ + "WW" + ], + "Interlingue" + ], + "ig": [ + "Latn", + [ + "AF" + ], + "Igbo" + ], + "ii": [ + "Yiii", + [ + "AS" + ], + "ꆇꉙ" + ], + "ik": [ + "Latn", + [ + "AM" + ], + "Iñupiak" + ], + "ike-cans": [ + "Cans", + [ + "AM" + ], + "ᐃᓄᒃᑎᑐᑦ" + ], + "ike-latn": [ + "Latn", + [ + "AM" + ], + "inuktitut" + ], + "ilo": [ + "Latn", + [ + "AS" + ], + "Ilokano" + ], + "inh": [ + "Cyrl", + [ + "EU" + ], + "гӀалгӀай" + ], + "io": [ + "Latn", + [ + "WW" + ], + "Ido" + ], + "is": [ + "Latn", + [ + "EU" + ], + "íslenska" + ], + "it": [ + "Latn", + [ + "EU" + ], + "italiano" + ], + "iu": [ + "ike-cans" + ], + "izh": [ + "Latn", + [ + "EU" + ], + "ižoran keel" + ], + "ja": [ + "Jpan", + [ + "AS" + ], + "日本語" + ], + "jam": [ + "Latn", + [ + "AM" + ], + "Patois" + ], + "jbo": [ + "Latn", + [ + "WW" + ], + "lojban" + ], + "jdt": [ + "jdt-cyrl" + ], + "jdt-cyrl": [ + "Cyrl", + [ + "EU", + "AS" + ], + "жугьури" + ], + "jje": [ + "Kore", + [ + "AS" + ], + "제주말" + ], + "jut": [ + "Latn", + [ + "EU" + ], + "jysk" + ], + "jv": [ + "Latn", + [ + "AS", + "PA" + ], + "Jawa" + ], + "jv-java": [ + "Java", + [ + "AS", + "PA" + ], + "ꦗꦮ" + ], + "ka": [ + "Geor", + [ + "EU" + ], + "ქართული" + ], + "kaa": [ + "Latn", + [ + "AS" + ], + "Qaraqalpaqsha" + ], + "kab": [ + "Latn", + [ + "AF", + "EU" + ], + "Taqbaylit" + ], + "kac": [ + "Latn", + [ + "AS" + ], + "Jinghpaw" + ], + "kbd-cyrl": [ + "kbd" + ], + "kbd-latn": [ + "Latn", + [ + "EU" + ], + "Qabardjajəbza" + ], + "kbd": [ + "Cyrl", + [ + "EU", + "ME" + ], + "адыгэбзэ" + ], + "kbp": [ + "Latn", + [ + "AF" + ], + "Kabɩyɛ" + ], + "kcg": [ + "Latn", + [ + "AF" + ], + "Tyap" + ], + "kck": [ + "Latn", + [ + "AF" + ], + "tjikalanga" + ], + "kea": [ + "Latn", + [ + "AF" + ], + "kabuverdianu" + ], + "ken": [ + "Latn", + [ + "AF" + ], + "kɛ́nyáŋ" + ], + "kg": [ + "Latn", + [ + "AF" + ], + "Kongo" + ], + "kgp": [ + "Latn", + [ + "AM" + ], + "Kaingáng" + ], + "khw": [ + "Arab", + [ + "ME", + "AS" + ], + "کھوار" + ], + "ki": [ + "Latn", + [ + "AF" + ], + "Gĩkũyũ" + ], + "kiu": [ + "Latn", + [ + "EU", + "ME" + ], + "Kırmancki" + ], + "kj": [ + "Latn", + [ + "AF" + ], + "Kwanyama" + ], + "kjh": [ + "Cyrl", + [ + "AS" + ], + "хакас" + ], + "kjp": [ + "Mymr", + [ + "AS" + ], + "ဖၠုံလိက်" + ], + "kk": [ + "kk-cyrl" + ], + "kk-arab": [ + "Arab", + [ + "EU", + "AS" + ], + "قازاقشا (تٶتە)" + ], + "kk-cn": [ + "kk-arab" + ], + "kk-cyrl": [ + "Cyrl", + [ + "EU", + "AS" + ], + "қазақша" + ], + "kk-kz": [ + "kk-cyrl" + ], + "kk-latn": [ + "Latn", + [ + "EU", + "AS", + "ME" + ], + "qazaqşa" + ], + "kk-tr": [ + "kk-latn" + ], + "kl": [ + "Latn", + [ + "AM", + "EU" + ], + "kalaallisut" + ], + "km": [ + "Khmr", + [ + "AS" + ], + "ភាសាខ្មែរ" + ], + "kn": [ + "Knda", + [ + "AS" + ], + "ಕನ್ನಡ" + ], + "knn": [ + "Deva", + [ + "AS" + ], + "महाराष्ट्रीय कोंकणी" + ], + "ko-kp": [ + "Kore", + [ + "AS" + ], + "조선말" + ], + "ko": [ + "Kore", + [ + "AS" + ], + "한국어" + ], + "koi": [ + "Cyrl", + [ + "EU" + ], + "перем коми" + ], + "koy": [ + "Latn", + [ + "AM" + ], + "Denaakkenaageʼ" + ], + "kr": [ + "Latn", + [ + "AF" + ], + "kanuri" + ], + "krc": [ + "Cyrl", + [ + "EU" + ], + "къарачай-малкъар" + ], + "kri": [ + "Latn", + [ + "AF" + ], + "Krio" + ], + "krj": [ + "Latn", + [ + "AS" + ], + "Kinaray-a" + ], + "krl": [ + "Latn", + [ + "EU" + ], + "Karjala" + ], + "ks-arab": [ + "Arab", + [ + "AS" + ], + "کٲشُر" + ], + "ks-deva": [ + "Deva", + [ + "AS" + ], + "कॉशुर" + ], + "ks": [ + "ks-arab" + ], + "ksf": [ + "Latn", + [ + "AF" + ], + "Bafia" + ], + "ksh": [ + "Latn", + [ + "EU" + ], + "Ripoarisch" + ], + "ksw": [ + "Mymr", + [ + "AS" + ], + "စှီၤ" + ], + "ku": [ + "ku-latn" + ], + "ku-arab": [ + "Arab", + [ + "EU", + "ME" + ], + "كوردي" + ], + "ku-latn": [ + "Latn", + [ + "EU", + "ME" + ], + "kurdî" + ], + "kum": [ + "Cyrl", + [ + "EU" + ], + "къумукъ" + ], + "kv": [ + "Cyrl", + [ + "EU" + ], + "коми" + ], + "kw": [ + "Latn", + [ + "EU" + ], + "kernowek" + ], + "ky": [ + "Cyrl", + [ + "AS" + ], + "кыргызча" + ], + "la": [ + "Latn", + [ + "EU" + ], + "Latina" + ], + "lad": [ + "lad-latn" + ], + "lad-latn": [ + "Latn", + [ + "ME", + "EU", + "AM" + ], + "Ladino" + ], + "lad-hebr": [ + "Hebr", + [ + "ME", + "EU", + "AM" + ], + "לאדינו" + ], + "lag": [ + "Latn", + [ + "AF" + ], + "Kilaangi" + ], + "lb": [ + "Latn", + [ + "EU" + ], + "Lëtzebuergesch" + ], + "lbe": [ + "Cyrl", + [ + "EU" + ], + "лакку" + ], + "lez": [ + "Cyrl", + [ + "EU" + ], + "лезги" + ], + "lfn": [ + "Latn", + [ + "WW" + ], + "Lingua Franca Nova" + ], + "lg": [ + "Latn", + [ + "AF" + ], + "Luganda" + ], + "li": [ + "Latn", + [ + "EU" + ], + "Limburgs" + ], + "lij": [ + "Latn", + [ + "EU" + ], + "Ligure" + ], + "liv": [ + "Latn", + [ + "EU" + ], + "Līvõ kēļ" + ], + "lki": [ + "Arab", + [ + "AS", + "ME" + ], + "لەکی" + ], + "lkt": [ + "Latn", + [ + "AM" + ], + "Lakȟótiyapi" + ], + "lld": [ + "Latn", + [ + "EU" + ], + "Ladin" + ], + "lmo": [ + "Latn", + [ + "EU" + ], + "lombard" + ], + "ln": [ + "Latn", + [ + "AF" + ], + "lingála" + ], + "lo": [ + "Laoo", + [ + "AS" + ], + "ລາວ" + ], + "loz": [ + "Latn", + [ + "AF" + ], + "Silozi" + ], + "lt": [ + "Latn", + [ + "EU" + ], + "lietuvių" + ], + "lrc": [ + "Arab", + [ + "AS", + "ME" + ], + "لۊری شومالی" + ], + "ltg": [ + "Latn", + [ + "EU" + ], + "latgaļu" + ], + "lud": [ + "Latn", + [ + "EU" + ], + "lüüdi" + ], + "lus": [ + "Latn", + [ + "AS" + ], + "Mizo ţawng" + ], + "lut": [ + "Latn", + [ + "AM" + ], + "dxʷləšucid" + ], + "luz": [ + "Arab", + [ + "ME" + ], + "لئری دوٙمینی" + ], + "lv": [ + "Latn", + [ + "EU" + ], + "latviešu" + ], + "lzh": [ + "Hant", + [ + "AS" + ], + "文言" + ], + "lzz": [ + "Latn", + [ + "EU", + "ME" + ], + "Lazuri" + ], + "mad": [ + "Latn", + [ + "AS" + ], + "Madhurâ" + ], + "mai": [ + "Deva", + [ + "AS" + ], + "मैथिली" + ], + "map-bms": [ + "Latn", + [ + "AS" + ], + "Basa Banyumasan" + ], + "mcn": [ + "Latn", + [ + "AF" + ], + "vùn màsànà" + ], + "mdf": [ + "Cyrl", + [ + "EU" + ], + "мокшень" + ], + "mfe": [ + "Latn", + [ + "AM" + ], + "Morisyen" + ], + "mg": [ + "Latn", + [ + "AF" + ], + "Malagasy" + ], + "mh": [ + "Latn", + [ + "PA" + ], + "Ebon" + ], + "mhr": [ + "Cyrl", + [ + "EU" + ], + "олык марий" + ], + "mi": [ + "Latn", + [ + "PA" + ], + "Māori" + ], + "mic": [ + "Latn", + [ + "AM" + ], + "Mi'kmaq" + ], + "min": [ + "Latn", + [ + "AS" + ], + "Minangkabau" + ], + "miq": [ + "Latn", + [ + "AM" + ], + "Mískitu" + ], + "mk": [ + "Cyrl", + [ + "EU" + ], + "македонски" + ], + "ml": [ + "Mlym", + [ + "AS", + "ME" + ], + "മലയാളം" + ], + "mn": [ + "Cyrl", + [ + "AS" + ], + "монгол" + ], + "mn-cyrl": [ + "mn" + ], + "mn-mong": [ + "mvf" + ], + "mnc": [ + "Mong", + [ + "AS" + ], + "ᠮᠠᠨᠵᡠ ᡤᡳᠰᡠᠨ" + ], + "mni": [ + "Mtei", + [ + "AS" + ], + "ꯃꯤꯇꯩ ꯂꯣꯟ" + ], + "mni-beng": [ + "Beng", + [ + "AS" + ], + "মেইতেই লোন্" + ], + "mnw": [ + "Mymr", + [ + "AS" + ], + "ဘာသာ မန်" + ], + "mo": [ + "Cyrl", + [ + "EU" + ], + "молдовеняскэ" + ], + "moe": [ + "Latn", + [ + "AM" + ], + "innu-aimun" + ], + "mos": [ + "Latn", + [ + "AF" + ], + "moore" + ], + "mr": [ + "Deva", + [ + "AS", + "ME" + ], + "मराठी" + ], + "mrh": [ + "Latn", + [ + "AS" + ], + "Mara" + ], + "mrj": [ + "Cyrl", + [ + "EU" + ], + "кырык мары" + ], + "mrv": [ + "Latn", + [ + "PA" + ], + "Magareva" + ], + "ms": [ + "Latn", + [ + "AS" + ], + "Bahasa Melayu" + ], + "ms-arab": [ + "Arab", + [ + "AS" + ], + "بهاس ملايو" + ], + "mt": [ + "Latn", + [ + "EU" + ], + "Malti" + ], + "mui": [ + "Latn", + [ + "AS" + ], + "Musi" + ], + "mus": [ + "Latn", + [ + "AM" + ], + "Mvskoke" + ], + "mvf": [ + "Mong", + [ + "AS" + ], + "ᠮᠣᠩᠭᠣᠯ" + ], + "mwl": [ + "Latn", + [ + "EU" + ], + "Mirandés" + ], + "mwv": [ + "Latn", + [ + "AS" + ], + "Behase Mentawei" + ], + "mww": [ + "mww-latn" + ], + "mww-latn": [ + "Latn", + [ + "AS" + ], + "Hmoob Dawb" + ], + "my": [ + "Mymr", + [ + "AS" + ], + "မြန်မာဘာသာ" + ], + "myv": [ + "Cyrl", + [ + "EU" + ], + "эрзянь" + ], + "mzn": [ + "Arab", + [ + "ME", + "AS" + ], + "مازِرونی" + ], + "na": [ + "Latn", + [ + "PA" + ], + "Dorerin Naoero" + ], + "nah": [ + "Latn", + [ + "AM" + ], + "Nāhuatl" + ], + "nan": [ + "Latn", + [ + "AS" + ], + "Bân-lâm-gú" + ], + "nan-hani": [ + "Hani", + [ + "AS" + ], + "閩南語(漢字)" + ], + "nap": [ + "Latn", + [ + "EU" + ], + "Napulitano" + ], + "nb": [ + "Latn", + [ + "EU" + ], + "norsk (bokmål)" + ], + "nd": [ + "Latn", + [ + "AF" + ], + "siNdebele saseNyakatho" + ], + "nds-nl": [ + "Latn", + [ + "EU" + ], + "Nedersaksisch" + ], + "nds": [ + "Latn", + [ + "EU" + ], + "Plattdüütsch" + ], + "ne": [ + "Deva", + [ + "AS" + ], + "नेपाली" + ], + "new": [ + "Deva", + [ + "AS" + ], + "नेपाल भाषा" + ], + "ng": [ + "Latn", + [ + "AF" + ], + "Oshiwambo" + ], + "nia": [ + "Latn", + [ + "AS" + ], + "Li Niha" + ], + "niu": [ + "Latn", + [ + "PA" + ], + "ko e vagahau Niuē" + ], + "njo": [ + "Latn", + [ + "AS" + ], + "Ao" + ], + "nl-informal": [ + "Latn", + [ + "EU", + "AM" + ], + "Nederlands (informeel)" + ], + "nl": [ + "Latn", + [ + "EU", + "AM" + ], + "Nederlands" + ], + "nmz": [ + "Latn", + [ + "AF" + ], + "nawdm" + ], + "nn": [ + "Latn", + [ + "EU" + ], + "norsk (nynorsk)" + ], + "no": [ + "Latn", + [ + "EU" + ], + "norsk" + ], + "nod": [ + "Lana", + [ + "AS" + ], + "ᨣᩴᩤᨾᩮᩥᩬᨦ" + ], + "nod-thai": [ + "Thai", + [ + "AS" + ], + "คำเมือง" + ], + "nog": [ + "Cyrl", + [ + "EU" + ], + "ногайша" + ], + "nov": [ + "Latn", + [ + "WW" + ], + "Novial" + ], + "nqo": [ + "Nkoo", + [ + "AF" + ], + "ߒߞߏ" + ], + "nr": [ + "Latn", + [ + "AF" + ], + "isiNdebele seSewula" + ], + "nrm": [ + "Latn", + [ + "EU" + ], + "Nouormand" + ], + "nso": [ + "Latn", + [ + "AF" + ], + "Sesotho sa Leboa" + ], + "nus": [ + "Latn", + [ + "AF" + ], + "Thok Naath" + ], + "nv": [ + "Latn", + [ + "AM" + ], + "Diné bizaad" + ], + "ny": [ + "Latn", + [ + "AF" + ], + "Chi-Chewa" + ], + "nyn": [ + "Latn", + [ + "AF" + ], + "runyankore" + ], + "nys": [ + "Latn", + [ + "PA" + ], + "Nyungar" + ], + "oc": [ + "Latn", + [ + "EU" + ], + "occitan" + ], + "ojb": [ + "Latn", + [ + "AM" + ], + "Ojibwemowin" + ], + "oka": [ + "Latn", + [ + "AM" + ], + "n̓səl̓xcin̓" + ], + "olo": [ + "Latn", + [ + "EU" + ], + "livvinkarjala" + ], + "om": [ + "Latn", + [ + "AF" + ], + "Oromoo" + ], + "ood": [ + "Latn", + [ + "AM" + ], + "ʼOʼodham ha-ñeʼokĭ" + ], + "or": [ + "Orya", + [ + "AS" + ], + "ଓଡ଼ିଆ" + ], + "os": [ + "Cyrl", + [ + "EU" + ], + "ирон" + ], + "osi": [ + "Latn", + [ + "AS" + ], + "Using" + ], + "ota": [ + "Arab", + [ + "AS", + "EU" + ], + "لسان عثمانى" + ], + "ovd": [ + "Latn", + [ + "EU" + ], + "övdalsk" + ], + "pa": [ + "pa-guru" + ], + "pa-guru": [ + "Guru", + [ + "AS" + ], + "ਪੰਜਾਬੀ" + ], + "pag": [ + "Latn", + [ + "AS" + ], + "Pangasinan" + ], + "pam": [ + "Latn", + [ + "AS" + ], + "Kapampangan" + ], + "pap": [ + "Latn", + [ + "AM" + ], + "Papiamentu" + ], + "pap-aw": [ + "Latn", + [ + "AM" + ], + "Papiamento" + ], + "pbb": [ + "Latn", + [ + "AM" + ], + "Nasa Yuwe" + ], + "pcd": [ + "Latn", + [ + "EU" + ], + "Picard" + ], + "pcm": [ + "Latn", + [ + "AF" + ], + "Naijá" + ], + "pdc": [ + "Latn", + [ + "EU", + "AM" + ], + "Deitsch" + ], + "pdt": [ + "Latn", + [ + "EU", + "AM" + ], + "Plautdietsch" + ], + "pfl": [ + "Latn", + [ + "EU" + ], + "Pälzisch" + ], + "pi": [ + "Deva", + [ + "AS" + ], + "पालि" + ], + "pih": [ + "Latn", + [ + "PA" + ], + "Norfuk \/ Pitkern" + ], + "pis": [ + "Latn", + [ + "PA" + ], + "Pijin" + ], + "pjt": [ + "Latn", + [ + "PA" + ], + "Pitjantjatjara" + ], + "pko": [ + "Latn", + [ + "AF" + ], + "Pökoot" + ], + "pl": [ + "Latn", + [ + "EU" + ], + "polski" + ], + "pms": [ + "Latn", + [ + "EU" + ], + "Piemontèis" + ], + "pnb": [ + "Arab", + [ + "AS", + "ME" + ], + "پنجابی" + ], + "pnt": [ + "Grek", + [ + "EU" + ], + "Ποντιακά" + ], + "pov": [ + "Latn", + [ + "AF" + ], + "guinensi" + ], + "ppl": [ + "Latn", + [ + "AM" + ], + "Nawat" + ], + "prg": [ + "Latn", + [ + "EU" + ], + "Prūsiskan" + ], + "prs": [ + "Arab", + [ + "AS", + "ME" + ], + "دری" + ], + "ps": [ + "Arab", + [ + "AS", + "ME" + ], + "پښتو" + ], + "pt-br": [ + "Latn", + [ + "AM" + ], + "português do Brasil" + ], + "pt": [ + "Latn", + [ + "EU", + "AM", + "AS", + "PA", + "AF", + "WW" + ], + "português" + ], + "pwn": [ + "Latn", + [ + "AS" + ], + "pinayuanan" + ], + "qu": [ + "Latn", + [ + "AM" + ], + "Runa Simi" + ], + "quc": [ + "Latn", + [ + "AM" + ], + "K'iche'" + ], + "qug": [ + "Latn", + [ + "AM" + ], + "Runa shimi" + ], + "qwh": [ + "Latn", + [ + "AM" + ], + "anqash qichwa" + ], + "rag": [ + "Latn", + [ + "AF" + ], + "Lologooli" + ], + "rap": [ + "Latn", + [ + "PA", + "AM" + ], + "arero rapa nui" + ], + "rcf": [ + "Latn", + [ + "AF" + ], + "Kreol Réyoné" + ], + "rej": [ + "Latn", + [ + "AS", + "PA" + ], + "Jang" + ], + "rgn": [ + "Latn", + [ + "EU" + ], + "Rumagnôl" + ], + "rhg": [ + "Rohg", + [ + "AS" + ], + "𐴌𐴟𐴇𐴥𐴝𐴚𐴒𐴙𐴝" + ], + "rif": [ + "Latn", + [ + "AF" + ], + "Tarifit" + ], + "rki": [ + "Mymr", + [ + "AS" + ], + "ရခိုင်" + ], + "rm": [ + "Latn", + [ + "EU" + ], + "rumantsch" + ], + "rmc": [ + "Latn", + [ + "EU" + ], + "romaňi čhib" + ], + "rmf": [ + "Latn", + [ + "EU" + ], + "kaalengo tšimb" + ], + "rmy": [ + "Latn", + [ + "EU" + ], + "Romani" + ], + "rn": [ + "Latn", + [ + "AF" + ], + "ikirundi" + ], + "ro": [ + "Latn", + [ + "EU" + ], + "română" + ], + "roa-rup": [ + "rup" + ], + "roa-tara": [ + "Latn", + [ + "EU" + ], + "tarandíne" + ], + "rsk": [ + "Cyrl", + [ + "EU" + ], + "руски" + ], + "rtm": [ + "Latn", + [ + "PA" + ], + "Faeag Rotuma" + ], + "ru": [ + "Cyrl", + [ + "EU", + "AS", + "ME" + ], + "русский" + ], + "rue": [ + "Cyrl", + [ + "EU" + ], + "русиньскый" + ], + "rup": [ + "Latn", + [ + "EU" + ], + "armãneashti" + ], + "ruq": [ + "Cyrl", + [ + "EU" + ], + "Влахесте" + ], + "ruq-cyrl": [ + "ruq" + ], + "ruq-grek": [ + "Grek", + [ + "EU" + ], + "Megleno-Romanian (Greek script)" + ], + "ruq-latn": [ + "Latn", + [ + "EU" + ], + "Vlăheşte" + ], + "rut": [ + "Cyrl", + [ + "EU" + ], + "мыхаӀбишды" + ], + "rw": [ + "Latn", + [ + "AF" + ], + "Ikinyarwanda" + ], + "rwr": [ + "Deva", + [ + "AS" + ], + "मारवाड़ी" + ], + "ryu": [ + "Jpan", + [ + "AS" + ], + "うちなーぐち" + ], + "sa": [ + "Deva", + [ + "AS" + ], + "संस्कृतम्" + ], + "sah": [ + "Cyrl", + [ + "EU", + "AS" + ], + "саха тыла" + ], + "sat": [ + "Olck", + [ + "AS" + ], + "ᱥᱟᱱᱛᱟᱲᱤ" + ], + "saz": [ + "Saur", + [ + "AS" + ], + "ꢱꣃꢬꢵꢯ꣄ꢡ꣄ꢬꢵ" + ], + "sc": [ + "Latn", + [ + "EU" + ], + "sardu" + ], + "scn": [ + "Latn", + [ + "EU" + ], + "sicilianu" + ], + "sco": [ + "Latn", + [ + "EU" + ], + "Scots" + ], + "sd": [ + "Arab", + [ + "AS" + ], + "سنڌي" + ], + "sdc": [ + "Latn", + [ + "EU" + ], + "Sassaresu" + ], + "sdh": [ + "Arab", + [ + "ME" + ], + "کوردی خوارگ" + ], + "se": [ + "Latn", + [ + "EU" + ], + "davvisámegiella" + ], + "se-fi": [ + "Latn", + [ + "EU" + ], + "davvisámegiella (Suoma bealde)" + ], + "se-no": [ + "Latn", + [ + "EU" + ], + "davvisámegiella (Norgga bealde)" + ], + "se-se": [ + "Latn", + [ + "EU" + ], + "davvisámegiella (Ruoŧa bealde)" + ], + "ses": [ + "Latn", + [ + "AF" + ], + "Koyraboro Senni" + ], + "sei": [ + "Latn", + [ + "AM" + ], + "Cmique Itom" + ], + "sg": [ + "Latn", + [ + "AF" + ], + "Sängö" + ], + "sgs": [ + "Latn", + [ + "EU" + ], + "žemaitėška" + ], + "sh": [ + "Latn", + [ + "EU" + ], + "srpskohrvatski" + ], + "shi-latn": [ + "Latn", + [ + "AF" + ], + "Taclḥit" + ], + "shi-tfng": [ + "Tfng", + [ + "AF" + ], + "ⵜⴰⵛⵍⵃⵉⵜ" + ], + "shi": [ + "shi-latn" + ], + "shn": [ + "Mymr", + [ + "AS" + ], + "လိၵ်ႈတႆး" + ], + "shy-latn": [ + "Latn", + [ + "AF" + ], + "tacawit" + ], + "shy": [ + "shy-latn" + ], + "si": [ + "Sinh", + [ + "AS" + ], + "සිංහල" + ], + "simple": [ + "en-simple" + ], + "sjd": [ + "Cyrl", + [ + "EU" + ], + "кӣллт са̄мь кӣлл" + ], + "sje": [ + "Latn", + [ + "EU" + ], + "bidumsámegiella" + ], + "sjo": [ + "Mong", + [ + "AS" + ], + "ᠰᡞᠪᡝ ᡤᡞᠰᡠᠨ" + ], + "sju": [ + "Latn", + [ + "EU" + ], + "ubmejesámiengiälla" + ], + "sk": [ + "Latn", + [ + "EU" + ], + "slovenčina" + ], + "sl": [ + "Latn", + [ + "EU" + ], + "slovenščina" + ], + "sli": [ + "Latn", + [ + "EU" + ], + "Schläsch" + ], + "slr": [ + "Latn", + [ + "AS" + ], + "Salırça" + ], + "sly": [ + "Latn", + [ + "AS" + ], + "Bahasa Selayar" + ], + "skr-arab": [ + "Arab", + [ + "AS" + ], + "سرائیکی" + ], + "skr": [ + "skr-arab" + ], + "srq": [ + "Latn", + [ + "AM" + ], + "mbia cheë" + ], + "syc": [ + "Syrc", + [ + "ME" + ], + "ܣܘܪܝܝܐ" + ], + "syl": [ + "Sylo", + [ + "AS" + ], + "ꠍꠤꠟꠐꠤ" + ], + "syl-beng": [ + "Beng", + [ + "AS" + ], + "সিলেটি" + ], + "syl-sylo": [ + "syl" + ], + "sm": [ + "Latn", + [ + "PA" + ], + "Gagana Samoa" + ], + "sma": [ + "Latn", + [ + "EU" + ], + "åarjelsaemien" + ], + "smj": [ + "Latn", + [ + "EU" + ], + "julevsámegiella" + ], + "smn": [ + "Latn", + [ + "EU" + ], + "anarâškielâ" + ], + "sms": [ + "Latn", + [ + "EU" + ], + "nuõrttsääʹmǩiõll" + ], + "sn": [ + "Latn", + [ + "AF" + ], + "chiShona" + ], + "so": [ + "Latn", + [ + "AF" + ], + "Soomaaliga" + ], + "son": [ + "Latn", + [ + "AF" + ], + "soŋay" + ], + "sq": [ + "Latn", + [ + "EU" + ], + "shqip" + ], + "sr": [ + "sr-cyrl" + ], + "sr-ec": [ + "sr-cyrl" + ], + "sr-cyrl": [ + "Cyrl", + [ + "EU" + ], + "српски" + ], + "sr-el": [ + "sr-latn" + ], + "sr-latn": [ + "Latn", + [ + "EU" + ], + "srpski" + ], + "srn": [ + "Latn", + [ + "AM", + "EU" + ], + "Sranantongo" + ], + "sro": [ + "Latn", + [ + "EU" + ], + "sardu campidanesu" + ], + "ss": [ + "Latn", + [ + "AF" + ], + "SiSwati" + ], + "st": [ + "Latn", + [ + "AF" + ], + "Sesotho" + ], + "stq": [ + "Latn", + [ + "EU" + ], + "Seeltersk" + ], + "sty": [ + "Cyrl", + [ + "EU", + "AS" + ], + "себертатар" + ], + "su": [ + "Latn", + [ + "AS" + ], + "Sunda" + ], + "sv": [ + "Latn", + [ + "EU" + ], + "svenska" + ], + "sw": [ + "Latn", + [ + "AF" + ], + "Kiswahili" + ], + "swb": [ + "Latn", + [ + "AF" + ], + "Shikomoro" + ], + "sxu": [ + "Latn", + [ + "EU" + ], + "Säggssch" + ], + "szl": [ + "Latn", + [ + "EU" + ], + "ślůnski" + ], + "szy": [ + "Latn", + [ + "AS" + ], + "Sakizaya" + ], + "ta": [ + "Taml", + [ + "AS" + ], + "தமிழ்" + ], + "tay": [ + "Latn", + [ + "AS" + ], + "Tayal" + ], + "tcy": [ + "Knda", + [ + "AS" + ], + "ತುಳು" + ], + "tdd": [ + "Tale", + [ + "AS" + ], + "ᥖᥭᥰᥖᥬᥳᥑᥨᥒᥰ" + ], + "te": [ + "Telu", + [ + "AS" + ], + "తెలుగు" + ], + "tet": [ + "Latn", + [ + "AS", + "PA" + ], + "tetun" + ], + "tg-cyrl": [ + "Cyrl", + [ + "AS" + ], + "тоҷикӣ" + ], + "tg-latn": [ + "Latn", + [ + "AS" + ], + "tojikī" + ], + "tg": [ + "tg-cyrl" + ], + "th": [ + "Thai", + [ + "AS" + ], + "ไทย" + ], + "ti": [ + "Ethi", + [ + "AF" + ], + "ትግርኛ" + ], + "tig": [ + "Ethi", + [ + "AF" + ], + "ትግረ" + ], + "tk": [ + "Latn", + [ + "AS" + ], + "Türkmençe" + ], + "tkr": [ + "Cyrl", + [ + "AS" + ], + "цӀаӀхна миз" + ], + "tl": [ + "Latn", + [ + "AS" + ], + "Tagalog" + ], + "tly": [ + "Latn", + [ + "EU", + "AS", + "ME" + ], + "tolışi" + ], + "tly-cyrl": [ + "Cyrl", + [ + "EU", + "AS", + "ME" + ], + "толыши" + ], + "tmr": [ + "Hebr", + [ + "ME", + "EU", + "AM" + ], + "ארמית בבלית" + ], + "tn": [ + "Latn", + [ + "AF" + ], + "Setswana" + ], + "to": [ + "Latn", + [ + "PA" + ], + "lea faka-Tonga" + ], + "tok": [ + "Latn", + [ + "WW" + ], + "toki pona" + ], + "tokipona": [ + "tok" + ], + "tpi": [ + "Latn", + [ + "PA", + "AS" + ], + "Tok Pisin" + ], + "tr": [ + "Latn", + [ + "EU", + "ME" + ], + "Türkçe" + ], + "trp": [ + "Latn", + [ + "AS" + ], + "Kokborok" + ], + "tru": [ + "Latn", + [ + "AS" + ], + "Ṫuroyo" + ], + "trv": [ + "Latn", + [ + "AS" + ], + "Seediq" + ], + "ts": [ + "Latn", + [ + "AF" + ], + "Xitsonga" + ], + "tsd": [ + "Grek", + [ + "EU" + ], + "Τσακωνικά" + ], + "tt": [ + "Cyrl", + [ + "EU" + ], + "татарча" + ], + "tt-cyrl": [ + "tt" + ], + "tt-latn": [ + "Latn", + [ + "EU" + ], + "tatarça" + ], + "ttt": [ + "Cyrl", + [ + "AS" + ], + "Tati" + ], + "tum": [ + "Latn", + [ + "AF" + ], + "chiTumbuka" + ], + "tw": [ + "Latn", + [ + "AF" + ], + "Twi" + ], + "twd": [ + "Latn", + [ + "EU" + ], + "Tweants" + ], + "ty": [ + "Latn", + [ + "PA" + ], + "reo tahiti" + ], + "tyv": [ + "Cyrl", + [ + "AS" + ], + "тыва дыл" + ], + "tzl": [ + "Latn", + [ + "WW" + ], + "Talossan" + ], + "tzm": [ + "Tfng", + [ + "AF" + ], + "ⵜⴰⵎⴰⵣⵉⵖⵜ" + ], + "udm": [ + "Cyrl", + [ + "EU" + ], + "удмурт" + ], + "ug": [ + "ug-arab" + ], + "ug-arab": [ + "Arab", + [ + "AS" + ], + "ئۇيغۇرچە" + ], + "ug-latn": [ + "Latn", + [ + "AS" + ], + "uyghurche" + ], + "ug-cyrl": [ + "Cyrl", + [ + "AS" + ], + "уйғурчә" + ], + "uk": [ + "Cyrl", + [ + "EU" + ], + "українська" + ], + "umu": [ + "Latn", + [ + "AM" + ], + "Huluníixsuwaakan" + ], + "ur": [ + "Arab", + [ + "AS", + "ME" + ], + "اردو" + ], + "uz": [ + "Latn", + [ + "AS" + ], + "oʻzbekcha" + ], + "uz-cyrl": [ + "Cyrl", + [ + "AS" + ], + "ўзбекча" + ], + "uz-latn": [ + "uz" + ], + "ve": [ + "Latn", + [ + "AF" + ], + "Tshivenda" + ], + "vai": [ + "Vaii", + [ + "AF" + ], + "ꕙꔤ" + ], + "vec": [ + "Latn", + [ + "EU", + "AM" + ], + "vèneto" + ], + "vep": [ + "Latn", + [ + "EU" + ], + "vepsän kel’" + ], + "vi": [ + "Latn", + [ + "AS" + ], + "Tiếng Việt" + ], + "vls": [ + "Latn", + [ + "EU" + ], + "West-Vlams" + ], + "vmf": [ + "Latn", + [ + "EU" + ], + "Mainfränkisch" + ], + "vmw": [ + "Latn", + [ + "AF" + ], + "emakhuwa" + ], + "vo": [ + "Latn", + [ + "WW" + ], + "Volapük" + ], + "vot": [ + "Latn", + [ + "EU" + ], + "Vaďďa" + ], + "vro": [ + "Latn", + [ + "EU" + ], + "võro" + ], + "wa": [ + "Latn", + [ + "EU" + ], + "walon" + ], + "wal": [ + "Latn", + [ + "AF" + ], + "wolaytta" + ], + "war": [ + "Latn", + [ + "AS" + ], + "Winaray" + ], + "wls": [ + "Latn", + [ + "PA" + ], + "Faka'uvea" + ], + "wo": [ + "Latn", + [ + "AF" + ], + "Wolof" + ], + "wuu": [ + "Hans", + [ + "AS" + ], + "吴语" + ], + "xal": [ + "Cyrl", + [ + "EU" + ], + "хальмг" + ], + "xh": [ + "Latn", + [ + "AF" + ], + "isiXhosa" + ], + "xmf": [ + "Geor", + [ + "EU" + ], + "მარგალური" + ], + "xsy": [ + "Latn", + [ + "AS" + ], + "SaiSiyat" + ], + "ydd": [ + "yi" + ], + "yi": [ + "Hebr", + [ + "ME", + "EU", + "AM" + ], + "ייִדיש" + ], + "yo": [ + "Latn", + [ + "AF" + ], + "Yorùbá" + ], + "yoi": [ + "Jpan", + [ + "AS" + ], + "与那国物言" + ], + "yrk": [ + "Cyrl", + [ + "AS" + ], + "ненэцяʼ вада" + ], + "yrl": [ + "Latn", + [ + "AM" + ], + "Nhẽẽgatú" + ], + "yua": [ + "Latn", + [ + "AM" + ], + "Maaya T'aan" + ], + "yue": [ + "Hant", + [ + "AS" + ], + "粵語" + ], + "za": [ + "Latn", + [ + "AS" + ], + "Vahcuengh" + ], + "zea": [ + "Latn", + [ + "EU" + ], + "Zeêuws" + ], + "zgh": [ + "Tfng", + [ + "AF" + ], + "ⵜⴰⵎⴰⵣⵉⵖⵜ ⵜⴰⵏⴰⵡⴰⵢⵜ" + ], + "zh": [ + "Hans", + [ + "AS", + "PA", + "AM" + ], + "中文" + ], + "zh-classical": [ + "lzh" + ], + "zh-cn": [ + "Hans", + [ + "AS" + ], + "中文(中国大陆)" + ], + "zh-hans": [ + "Hans", + [ + "AS", + "PA", + "AM" + ], + "中文(简体)" + ], + "zh-hant": [ + "Hant", + [ + "AS", + "PA", + "AM" + ], + "中文(繁體)" + ], + "zh-hk": [ + "Hant", + [ + "AS" + ], + "中文(香港)" + ], + "zh-min-nan": [ + "nan" + ], + "zh-mo": [ + "Hant", + [ + "AS" + ], + "中文(澳門)" + ], + "zh-my": [ + "Hans", + [ + "AS" + ], + "中文(马来西亚)" + ], + "zh-sg": [ + "Hans", + [ + "AS" + ], + "中文(新加坡)" + ], + "zh-tw": [ + "Hant", + [ + "AS" + ], + "中文(台灣)" + ], + "zh-yue": [ + "yue" + ], + "zh-cdo": [ + "cdo" + ], + "zu": [ + "Latn", + [ + "AF" + ], + "isiZulu" + ], + "zun": [ + "Latn", + [ + "AM" + ], + "Shiwi'ma" + ] + }, + "scriptgroups": { + "Latin": [ + "Latn", + "Goth" + ], + "Greek": [ + "Grek" + ], + "WestCaucasian": [ + "Armn", + "Geor" + ], + "Arabic": [ + "Arab" + ], + "MiddleEastern": [ + "Hebr", + "Syrc" + ], + "African": [ + "Copt", + "Ethi", + "Nkoo", + "Tfng", + "Vaii" + ], + "SouthAsian": [ + "Beng", + "Cakm", + "Deva", + "Gujr", + "Guru", + "Knda", + "Mlym", + "Mtei", + "Olck", + "Orya", + "Rohg", + "Saur", + "Sinh", + "Sylo", + "Taml", + "Telu", + "Tibt", + "Thaa", + "Wara" + ], + "Cyrillic": [ + "Cyrl" + ], + "CJK": [ + "Hani", + "Hans", + "Hant", + "Kore", + "Jpan", + "Yiii" + ], + "SouthEastAsian": [ + "Bali", + "Batk", + "Bugi", + "Java", + "Khmr", + "Laoo", + "Lana", + "Mymr", + "Thai", + "Tale" + ], + "Mongolian": [ + "Mong" + ], + "SignWriting": [ + "Sgnw" + ], + "NativeAmerican": [ + "Cher", + "Cans" + ], + "Special": [ + "Zyyy" + ] + }, + "rtlscripts": [ + "Arab", + "Hebr", + "Syrc", + "Nkoo", + "Rohg", + "Thaa" + ], + "regiongroups": { + "WW": 1, + "SP": 1, + "AM": 2, + "EU": 3, + "ME": 3, + "AF": 3, + "AS": 4, + "PA": 4 + }, + "territories": { + "AC": [ + "en" + ], + "AD": [ + "ca", + "es", + "fr" + ], + "AE": [ + "ar", + "en", + "ml", + "ps", + "fa" + ], + "AF": [ + "fa", + "ps", + "uz", + "tk", + "bgn", + "ug-arab", + "kk-arab", + "kk-cyrl" + ], + "AG": [ + "en", + "pt" + ], + "AI": [ + "en" + ], + "AL": [ + "sq", + "el", + "mk" + ], + "AM": [ + "hy", + "ku-latn", + "az-latn" + ], + "AO": [ + "pt", + "ln" + ], + "AR": [ + "es", + "en", + "cy", + "gn" + ], + "AS": [ + "sm", + "en" + ], + "AT": [ + "de", + "bar", + "en", + "fr", + "it", + "hr", + "sl", + "hu" + ], + "AU": [ + "en", + "zh-hant", + "zh", + "it" + ], + "AW": [ + "nl", + "pap", + "en" + ], + "AX": [ + "sv" + ], + "AZ": [ + "az-latn", + "az-cyrl", + "tly", + "ku-latn", + "ttt", + "tkr" + ], + "BA": [ + "bs", + "en", + "hr", + "sr-cyrl", + "sr-latn" + ], + "BB": [ + "en" + ], + "BD": [ + "bn", + "en", + "syl", + "rhg", + "ccp", + "my", + "mni" + ], + "BE": [ + "en", + "nl", + "fr", + "de", + "vls", + "wa" + ], + "BF": [ + "mos", + "fr", + "ff" + ], + "BG": [ + "bg", + "en", + "ru", + "tr", + "de" + ], + "BH": [ + "ar", + "ml" + ], + "BI": [ + "rn", + "fr", + "sw", + "en" + ], + "BJ": [ + "fr", + "fon", + "yo" + ], + "BL": [ + "fr" + ], + "BM": [ + "en" + ], + "BN": [ + "ms", + "zh-hant", + "zh", + "ms-arab", + "en" + ], + "BO": [ + "es", + "qu", + "ay", + "gn", + "aro" + ], + "BQ": [ + "pap", + "nl" + ], + "BR": [ + "pt", + "en", + "de", + "it", + "ja", + "es", + "kgp", + "ko", + "yrl" + ], + "BS": [ + "en" + ], + "BT": [ + "dz", + "ne", + "en" + ], + "BW": [ + "en", + "tn", + "af" + ], + "BY": [ + "be", + "ru" + ], + "BZ": [ + "en", + "es" + ], + "CA": [ + "en", + "fr", + "zh", + "yue", + "es", + "pa-guru", + "ar", + "tl", + "it", + "de", + "ur", + "fa", + "pt", + "ru", + "hi", + "ta", + "vi", + "pl", + "ko", + "gu", + "el", + "ro", + "bn", + "pdt", + "uk", + "sr-cyrl", + "nl", + "ja", + "hu", + "so", + "hr", + "ike-cans", + "tr", + "moe", + "cr", + "mic", + "atj", + "war", + "oka" + ], + "CC": [ + "ms-arab", + "ms", + "en" + ], + "CD": [ + "sw", + "fr", + "ln", + "kg", + "rw" + ], + "CF": [ + "fr", + "sg", + "ln" + ], + "CG": [ + "fr", + "ln" + ], + "CH": [ + "de", + "gsw", + "en", + "fr", + "it", + "lmo", + "pt", + "rm" + ], + "CI": [ + "fr", + "bci" + ], + "CK": [ + "en" + ], + "CL": [ + "es", + "en", + "arn" + ], + "CM": [ + "fr", + "en", + "bum", + "ff", + "bkm", + "bas", + "ar", + "ksf", + "agq", + "ha-arab", + "ha" + ], + "CN": [ + "zh", + "wuu", + "yue", + "hsn", + "hak", + "nan", + "gan", + "ii", + "ug-arab", + "za", + "mvf", + "mn", + "bo", + "ko", + "kk-arab", + "kk-cyrl", + "ky", + "tdd", + "en", + "ru", + "vi", + "uz-cyrl", + "uz", + "lzh" + ], + "CO": [ + "es", + "guc", + "yrl" + ], + "CR": [ + "es" + ], + "CU": [ + "es" + ], + "CV": [ + "kea", + "pt" + ], + "CW": [ + "pap", + "nl", + "es" + ], + "CX": [ + "en" + ], + "CY": [ + "el", + "en", + "tr", + "fr", + "hy", + "ar" + ], + "CZ": [ + "cs", + "en", + "sk", + "de", + "pl" + ], + "DE": [ + "de", + "en", + "fr", + "bar", + "nds", + "nl", + "it", + "es", + "ru", + "vmf", + "tr", + "gsw", + "da", + "hr", + "ku-latn", + "el", + "ksh", + "pl", + "hsb", + "frr", + "dsb", + "stq", + "pfl" + ], + "DG": [ + "en" + ], + "DJ": [ + "aa", + "so", + "ar", + "fr" + ], + "DK": [ + "da", + "en", + "de", + "sv", + "fo", + "kl", + "jut" + ], + "DM": [ + "en" + ], + "DO": [ + "es", + "en" + ], + "DZ": [ + "arq", + "ar", + "fr", + "kab", + "en" + ], + "EA": [ + "es" + ], + "EC": [ + "es", + "qu", + "qug" + ], + "EE": [ + "et", + "ru", + "en", + "fi", + "vro" + ], + "EG": [ + "ar", + "arz", + "en", + "el" + ], + "EH": [ + "ar" + ], + "ER": [ + "ti", + "en", + "tig", + "ar", + "aa", + "byn" + ], + "ES": [ + "es", + "en", + "ca", + "gl", + "eu", + "ast", + "ext", + "an", + "oc" + ], + "ET": [ + "en", + "am", + "om", + "so", + "ti", + "wal", + "aa" + ], + "FI": [ + "fi", + "en", + "sv", + "de", + "ru", + "et", + "rmf", + "se", + "smn", + "sms" + ], + "FJ": [ + "en", + "hi", + "hif", + "fj", + "rtm" + ], + "FK": [ + "en" + ], + "FM": [ + "en" + ], + "FO": [ + "fo" + ], + "FR": [ + "fr", + "en", + "es", + "de", + "oc", + "it", + "pt", + "pcd", + "gsw", + "br", + "co", + "ca", + "nl", + "eu", + "frp", + "ia" + ], + "GA": [ + "fr" + ], + "GB": [ + "en", + "fr", + "de", + "es", + "pl", + "pa-guru", + "ur", + "ta", + "gu", + "sco", + "cy", + "bn", + "ar", + "zh-hant", + "zh", + "it", + "lt", + "pt", + "so", + "tr", + "ga", + "gd", + "kw" + ], + "GD": [ + "en" + ], + "GE": [ + "ka", + "xmf", + "ru", + "hy", + "ab", + "os", + "ku-latn" + ], + "GF": [ + "fr", + "gcr", + "zh-hant", + "zh" + ], + "GG": [ + "en" + ], + "GH": [ + "ak", + "en", + "ee", + "gur", + "gaa", + "ha", + "ff" + ], + "GI": [ + "en", + "es" + ], + "GL": [ + "kl", + "da" + ], + "GM": [ + "en", + "ff" + ], + "GN": [ + "fr", + "ff", + "nqo" + ], + "GP": [ + "fr" + ], + "GQ": [ + "es", + "fan", + "fr", + "pt" + ], + "GR": [ + "el", + "en", + "fr", + "de", + "pnt", + "mk", + "tr", + "bg", + "sq", + "tsd" + ], + "GT": [ + "es", + "quc" + ], + "GU": [ + "en", + "ch" + ], + "GW": [ + "pt", + "ff" + ], + "GY": [ + "en" + ], + "HK": [ + "zh-hant", + "zh", + "yue", + "en" + ], + "HN": [ + "es", + "en" + ], + "HR": [ + "hr", + "en", + "it" + ], + "HT": [ + "ht", + "fr" + ], + "HU": [ + "hu", + "en", + "de", + "fr", + "ro", + "hr", + "sk", + "sl" + ], + "IC": [ + "es" + ], + "ID": [ + "id", + "jv", + "su", + "mad", + "ms", + "min", + "bew", + "ban", + "bug", + "bjn", + "ace", + "ms-arab", + "bbc-latn", + "zh-hant", + "zh", + "rej", + "gor", + "sly", + "mwv" + ], + "IE": [ + "en", + "ga", + "fr" + ], + "IL": [ + "he", + "en", + "ar", + "ru", + "ro", + "yi", + "pl", + "lad-latn", + "hu", + "am", + "ti", + "ml" + ], + "IM": [ + "en", + "gv" + ], + "IN": [ + "hi", + "en", + "bn", + "te", + "mr", + "ta", + "ur", + "gu", + "kn", + "ml", + "or", + "pa-guru", + "bho", + "awa", + "as", + "mai", + "hne", + "ne", + "sat", + "ks-arab", + "gom-deva", + "gbm", + "sd", + "doi", + "tcy", + "brx", + "mni", + "hoc", + "saz", + "ccp", + "bfq", + "njo", + "bo", + "bpy", + "bft", + "sa", + "dv", + "dz" + ], + "IO": [ + "en" + ], + "IQ": [ + "ar", + "en", + "ckb", + "az-arab", + "az-latn", + "fa", + "lrc" + ], + "IR": [ + "fa", + "az-arab", + "az-latn", + "mzn", + "glk", + "ckb", + "sdh", + "tk", + "lrc", + "ar", + "bqi", + "luz", + "lki", + "bgn", + "hy", + "ps", + "ka", + "gbz", + "kk-arab", + "kk-cyrl" + ], + "IS": [ + "is", + "da" + ], + "IT": [ + "it", + "en", + "fr", + "sc", + "de", + "vec", + "nap", + "lij", + "scn", + "sl", + "sdc", + "fur", + "egl", + "ca", + "el", + "lmo", + "pms", + "hr", + "rgn" + ], + "JE": [ + "en" + ], + "JM": [ + "en", + "jam" + ], + "JO": [ + "ar", + "en" + ], + "JP": [ + "ja", + "ryu", + "ko" + ], + "KE": [ + "sw", + "en", + "ki", + "so", + "pko", + "om", + "ar", + "pa-guru", + "gu" + ], + "KG": [ + "ky", + "ru" + ], + "KH": [ + "km" + ], + "KI": [ + "en" + ], + "KM": [ + "ar", + "fr" + ], + "KN": [ + "en" + ], + "KP": [ + "ko" + ], + "KR": [ + "ko" + ], + "KW": [ + "ar" + ], + "KY": [ + "en" + ], + "KZ": [ + "ru", + "kk-cyrl", + "en", + "de", + "ug-cyrl", + "ug-arab" + ], + "LA": [ + "lo" + ], + "LB": [ + "ar", + "en", + "hy", + "ku-arab", + "ku-latn", + "fr" + ], + "LC": [ + "en" + ], + "LI": [ + "de", + "gsw" + ], + "LK": [ + "si", + "ta", + "en" + ], + "LR": [ + "en", + "vai", + "ff" + ], + "LS": [ + "st", + "en", + "zu", + "ss", + "xh" + ], + "LT": [ + "lt", + "ru", + "en", + "de", + "sgs" + ], + "LU": [ + "fr", + "lb", + "de", + "en", + "pt" + ], + "LV": [ + "lv", + "en", + "ru", + "ltg" + ], + "LY": [ + "ar" + ], + "MA": [ + "ary", + "ar", + "zgh", + "fr", + "en", + "tzm", + "shi-latn", + "rif", + "es" + ], + "MC": [ + "fr" + ], + "MD": [ + "ro", + "uk", + "bg", + "gag", + "ru" + ], + "ME": [ + "sr-latn", + "sr-cyrl", + "sq" + ], + "MF": [ + "fr" + ], + "MG": [ + "mg", + "fr", + "en" + ], + "MH": [ + "en", + "mh" + ], + "MK": [ + "mk", + "sq", + "tr" + ], + "ML": [ + "bm", + "fr", + "ses", + "ar" + ], + "MM": [ + "my", + "shn", + "kac", + "rhg", + "mnw" + ], + "MN": [ + "mn", + "kk-arab", + "kk-cyrl", + "zh", + "ru", + "ug-cyrl", + "ug-arab" + ], + "MO": [ + "zh-hant", + "zh", + "pt", + "en" + ], + "MP": [ + "en", + "ch" + ], + "MQ": [ + "fr" + ], + "MR": [ + "ar", + "fr", + "ff", + "wo" + ], + "MS": [ + "en" + ], + "MT": [ + "mt", + "en", + "it", + "fr" + ], + "MU": [ + "mfe", + "en", + "bho", + "ur", + "fr", + "ta" + ], + "MV": [ + "dv", + "en" + ], + "MW": [ + "en", + "ny", + "tum", + "zu" + ], + "MX": [ + "es", + "en", + "yua", + "sei" + ], + "MY": [ + "ms", + "en", + "zh", + "ta", + "jv", + "dtp", + "ml", + "bug", + "bjn" + ], + "MZ": [ + "pt", + "vmw", + "ts", + "ny", + "sw", + "zu" + ], + "NA": [ + "af", + "kj", + "ng", + "hz", + "en", + "de", + "tn" + ], + "NC": [ + "fr" + ], + "NE": [ + "ha", + "fr", + "ar", + "ff" + ], + "NF": [ + "en" + ], + "NG": [ + "en", + "pcm", + "ha", + "ig", + "yo", + "efi", + "ha-arab", + "kcg", + "ar", + "ann", + "ff" + ], + "NI": [ + "es" + ], + "NL": [ + "nl", + "en", + "de", + "fr", + "nds", + "li", + "fy", + "id", + "zea", + "rif", + "tr" + ], + "NO": [ + "nb", + "no", + "nn", + "se" + ], + "NP": [ + "ne", + "mai", + "bho", + "new", + "en", + "dty", + "awa", + "hi", + "bo", + "bn" + ], + "NR": [ + "en", + "na" + ], + "NU": [ + "en", + "niu" + ], + "NZ": [ + "en", + "mi" + ], + "OM": [ + "ar", + "fa" + ], + "PA": [ + "es", + "en", + "zh-hant", + "zh" + ], + "PE": [ + "es", + "qu", + "ay" + ], + "PF": [ + "fr", + "ty", + "zh-hant", + "zh" + ], + "PG": [ + "tpi", + "en", + "ho" + ], + "PH": [ + "en", + "tl", + "es", + "ceb", + "ilo", + "hil", + "war", + "pam", + "pag", + "zh-hant", + "zh", + "cps", + "krj", + "bto" + ], + "PK": [ + "ur", + "pa-guru", + "en", + "ps", + "sd", + "skr-arab", + "brh", + "fa", + "bgn", + "bft", + "ks-arab", + "khw" + ], + "PL": [ + "pl", + "en", + "de", + "ru", + "szl", + "be", + "uk", + "csb", + "sli", + "lt" + ], + "PM": [ + "fr", + "en" + ], + "PN": [ + "en" + ], + "PR": [ + "es", + "en" + ], + "PS": [ + "ar" + ], + "PT": [ + "pt", + "en", + "fr", + "es", + "gl" + ], + "PW": [ + "en" + ], + "PY": [ + "gn", + "es", + "de" + ], + "QA": [ + "ar", + "fa", + "ml" + ], + "RE": [ + "fr", + "rcf", + "ta" + ], + "RO": [ + "ro", + "en", + "fr", + "es", + "hu", + "de", + "tr", + "sr-latn", + "sr-cyrl", + "bg", + "el", + "pl" + ], + "RS": [ + "sr-cyrl", + "sr-latn", + "sq", + "hu", + "ro", + "hr", + "sk", + "uk" + ], + "RU": [ + "ru", + "tt", + "ba", + "cv", + "hy", + "ce", + "av", + "udm", + "mhr", + "sah", + "os", + "kbd", + "myv", + "dar", + "mdf", + "kum", + "kv", + "lez", + "krc", + "inh", + "tyv", + "az-cyrl", + "az-latn", + "ady", + "krl", + "lbe", + "koi", + "mrj", + "alt", + "fi", + "sr-latn", + "sr-cyrl", + "vep", + "mn", + "izh", + "vot", + "cu" + ], + "RW": [ + "rw", + "en", + "fr" + ], + "SA": [ + "ar" + ], + "SB": [ + "en", + "pis" + ], + "SC": [ + "fr", + "en" + ], + "SD": [ + "ar", + "en", + "fvr", + "ha-arab", + "ha" + ], + "SE": [ + "sv", + "en", + "fi", + "fit", + "se", + "yi", + "smj", + "sma", + "ia" + ], + "SG": [ + "en", + "zh", + "ms", + "ta", + "ml", + "pa-guru" + ], + "SH": [ + "en" + ], + "SI": [ + "sl", + "hr", + "en", + "de", + "hu", + "it" + ], + "SJ": [ + "nb", + "ru" + ], + "SK": [ + "sk", + "cs", + "en", + "de", + "hu", + "uk", + "pl" + ], + "SL": [ + "kri", + "en", + "ff" + ], + "SM": [ + "it", + "eo" + ], + "SN": [ + "wo", + "fr", + "ff" + ], + "SO": [ + "so", + "ar", + "sw", + "om" + ], + "SR": [ + "nl", + "srn", + "zh-hant", + "zh" + ], + "SS": [ + "ar", + "en", + "nus" + ], + "ST": [ + "pt" + ], + "SV": [ + "es" + ], + "SX": [ + "en", + "es", + "nl" + ], + "SY": [ + "ar", + "ku-latn", + "fr", + "hy" + ], + "SZ": [ + "en", + "ss", + "zu", + "ts" + ], + "TA": [ + "en" + ], + "TC": [ + "en" + ], + "TD": [ + "fr", + "ar" + ], + "TF": [ + "fr" + ], + "TG": [ + "fr", + "ee" + ], + "TH": [ + "th", + "en", + "nod", + "zh-hant", + "zh", + "mnw", + "shn" + ], + "TJ": [ + "tg-cyrl", + "ru", + "fa", + "ar" + ], + "TK": [ + "en" + ], + "TL": [ + "pt", + "tet" + ], + "TM": [ + "tk", + "ru", + "uz", + "ku-latn" + ], + "TN": [ + "aeb-arab", + "ar", + "fr" + ], + "TO": [ + "to", + "en" + ], + "TR": [ + "tr", + "en", + "ku-latn", + "kbd", + "az-latn", + "az-arab", + "ar", + "bg", + "ady", + "kiu", + "hy", + "ka", + "sr-latn", + "sr-cyrl", + "lzz", + "sq", + "ab", + "el", + "tru", + "uz", + "ky", + "kk-cyrl" + ], + "TT": [ + "en", + "es" + ], + "TV": [ + "en" + ], + "TW": [ + "zh-hant", + "zh", + "trv" + ], + "TZ": [ + "sw", + "en", + "lag" + ], + "UA": [ + "uk", + "ru", + "pl", + "yi", + "rue", + "be", + "crh", + "ro", + "bg", + "tr", + "hu", + "el" + ], + "UG": [ + "sw", + "lg", + "nyn", + "en", + "rw", + "hi" + ], + "UM": [ + "en" + ], + "US": [ + "en", + "es", + "zh-hant", + "zh", + "fr", + "de", + "tl", + "it", + "vi", + "ko", + "ru", + "nv", + "yi", + "pdc", + "haw", + "frc", + "chr", + "esu", + "cho", + "lkt", + "ik", + "mus" + ], + "UY": [ + "es" + ], + "UZ": [ + "uz", + "uz-cyrl", + "ru", + "kaa", + "tr" + ], + "VA": [ + "it", + "la" + ], + "VC": [ + "en" + ], + "VE": [ + "es", + "yrl" + ], + "VG": [ + "en" + ], + "VI": [ + "en" + ], + "VN": [ + "vi", + "zh-hant", + "zh" + ], + "VU": [ + "bi", + "en", + "fr" + ], + "WF": [ + "wls", + "fr" + ], + "WS": [ + "sm", + "en" + ], + "XK": [ + "sq", + "aln", + "sr-cyrl", + "sr-latn" + ], + "YE": [ + "ar", + "en" + ], + "YT": [ + "swb", + "fr", + "sw" + ], + "ZA": [ + "en", + "zu", + "xh", + "af", + "nso", + "tn", + "st", + "ts", + "ss", + "ve", + "hi", + "nr", + "sw" + ], + "ZM": [ + "en", + "ny", + "loz" + ], + "ZW": [ + "sn", + "en", + "nd", + "kck", + "ny", + "ve", + "tn" + ] + } +} + + }( jQuery ) ); + }, {} ] +}, {}, [ 1 ] ); diff --git a/vendor/assets/jquery.uls/src/jquery.uls.data.utils.js b/vendor/assets/jquery.uls/src/jquery.uls.data.utils.js new file mode 100644 index 0000000000..9c34f9b975 --- /dev/null +++ b/vendor/assets/jquery.uls/src/jquery.uls.data.utils.js @@ -0,0 +1,335 @@ +/** + * Utility functions for querying language data. + * + * Copyright (C) 2012 Alolita Sharma, Amir Aharoni, Arun Ganesh, Brandon Harris, + * Niklas Laxström, Pau Giner, Santhosh Thottingal, Siebrand Mazeland and other + * contributors. See CREDITS for a list. + * + * UniversalLanguageSelector is dual licensed GPLv2 or later and MIT. You don't + * have to do anything special to choose one license or the other and you don't + * have to notify anyone which license you are using. You are free to use + * UniversalLanguageSelector in commercial projects as long as the copyright + * header is left intact. See files GPL-LICENSE and MIT-LICENSE for details. + * + * @file + * @license GNU General Public Licence 2.0 or later + * @license MIT License + */ + +( function ( $ ) { + 'use strict'; + + /** + * Is this language a redirect to another language? + * + * @param {string} language Language code + * @return {string|boolean} Target language code if it's a redirect or false if it's not + */ + $.uls.data.isRedirect = function ( language ) { + return ( $.uls.data.languages[ language ] !== undefined && + $.uls.data.languages[ language ].length === 1 ) ? + $.uls.data.languages[ language ][ 0 ] : false; + }; + + /** + * Returns the script of the language. + * + * @param {string} language Language code + * @return {string} + */ + $.uls.data.getScript = function ( language ) { + var target = $.uls.data.isRedirect( language ); + + if ( target ) { + return $.uls.data.getScript( target ); + } + + if ( !$.uls.data.languages[ language ] ) { + // Undetermined + return 'Zyyy'; + } + + return $.uls.data.languages[ language ][ 0 ]; + }; + + /** + * Returns the regions in which a language is spoken. + * + * @param {string} language Language code + * @return {string|string[]} + */ + $.uls.data.getRegions = function ( language ) { + var target = $.uls.data.isRedirect( language ); + + if ( target ) { + return $.uls.data.getRegions( target ); + } + + return ( $.uls.data.languages[ language ] && $.uls.data.languages[ language ][ 1 ] ) || 'UNKNOWN'; + }; + + /** + * Returns the autonym of the language. + * + * @param {string} language Language code + * @return {string} + */ + $.uls.data.getAutonym = function ( language ) { + var target = $.uls.data.isRedirect( language ); + + if ( target ) { + return $.uls.data.getAutonym( target ); + } + + return ( $.uls.data.languages[ language ] && + $.uls.data.languages[ language ][ 2 ] ) || language; + }; + + /** + * Returns all language codes and corresponding autonyms + * + * @return {string[]} + */ + $.uls.data.getAutonyms = function () { + var language, + autonymsByCode = {}; + + for ( language in $.uls.data.languages ) { + if ( $.uls.data.isRedirect( language ) ) { + continue; + } + + autonymsByCode[ language ] = $.uls.data.getAutonym( language ); + } + + return autonymsByCode; + }; + + /** + * Returns all languages written in script. + * + * @param {string} script string + * @return {string[]} languages codes + */ + $.uls.data.getLanguagesInScript = function ( script ) { + return $.uls.data.getLanguagesInScripts( [ script ] ); + }; + + /** + * Returns all languages written in the given scripts. + * + * @param {string[]} scripts + * @return {string[]} languages codes + */ + $.uls.data.getLanguagesInScripts = function ( scripts ) { + var language, i, + languagesInScripts = []; + + for ( language in $.uls.data.languages ) { + if ( $.uls.data.isRedirect( language ) ) { + continue; + } + + for ( i = 0; i < scripts.length; i++ ) { + if ( scripts[ i ] === $.uls.data.getScript( language ) ) { + languagesInScripts.push( language ); + break; + } + } + } + + return languagesInScripts; + }; + + /** + * Returns an associative array of languages in a region, + * grouped by script group. + * + * @param {string} region Region code + * @return {Object} + */ + $.uls.data.getLanguagesByScriptGroupInRegion = function ( region ) { + return $.uls.data.getLanguagesByScriptGroupInRegions( [ region ] ); + }; + + /** + * Get the given list of languages grouped by script. + * + * @param {string[]} languages Array of language codes to group + * @return {string[]} Array of language codes + */ + $.uls.data.getLanguagesByScriptGroup = function ( languages ) { + var languagesByScriptGroup = {}, + language, languageIndex, resolvedRedirect, langScriptGroup; + + for ( languageIndex = 0; languageIndex < languages.length; languageIndex++ ) { + language = languages[ languageIndex ]; + resolvedRedirect = $.uls.data.isRedirect( language ) || language; + langScriptGroup = $.uls.data.getScriptGroupOfLanguage( resolvedRedirect ); + if ( !languagesByScriptGroup[ langScriptGroup ] ) { + languagesByScriptGroup[ langScriptGroup ] = []; + } + languagesByScriptGroup[ langScriptGroup ].push( language ); + } + return languagesByScriptGroup; + }; + + /** + * Returns an associative array of languages in several regions, + * grouped by script group. + * + * @param {string[]} regions region codes + * @return {Object} + */ + $.uls.data.getLanguagesByScriptGroupInRegions = function ( regions ) { + var language, i, scriptGroup, + languagesByScriptGroupInRegions = {}; + + for ( language in $.uls.data.languages ) { + if ( $.uls.data.isRedirect( language ) ) { + continue; + } + + for ( i = 0; i < regions.length; i++ ) { + if ( $.uls.data.getRegions( language ).indexOf( regions[ i ] ) !== -1 ) { + scriptGroup = $.uls.data.getScriptGroupOfLanguage( language ); + + if ( languagesByScriptGroupInRegions[ scriptGroup ] === undefined ) { + languagesByScriptGroupInRegions[ scriptGroup ] = []; + } + + languagesByScriptGroupInRegions[ scriptGroup ].push( language ); + break; + } + } + } + + return languagesByScriptGroupInRegions; + }; + + /** + * Returns the script group of a script or 'Other' if it doesn't + * belong to any group. + * + * @param {string} script Script code + * @return {string} script group name + */ + $.uls.data.getGroupOfScript = function ( script ) { + var scriptGroup; + + for ( scriptGroup in $.uls.data.scriptgroups ) { + if ( $.uls.data.scriptgroups[ scriptGroup ].indexOf( script ) !== -1 ) { + return scriptGroup; + } + } + + return 'Other'; + }; + + /** + * Returns the script group of a language. + * + * @param {string} language Language code + * @return {string} script group name + */ + $.uls.data.getScriptGroupOfLanguage = function ( language ) { + return $.uls.data.getGroupOfScript( $.uls.data.getScript( language ) ); + }; + + /** + * Return the list of languages sorted by script groups. + * + * @param {string[]} languages Array of language codes to sort + * @return {string[]} Array of language codes + */ + $.uls.data.sortByScriptGroup = function ( languages ) { + var groupedLanguages, scriptGroups, i, + allLanguages = []; + + groupedLanguages = $.uls.data.getLanguagesByScriptGroup( languages ); + scriptGroups = Object.keys( groupedLanguages ).sort(); + + for ( i = 0; i < scriptGroups.length; i++ ) { + allLanguages = allLanguages.concat( groupedLanguages[ scriptGroups[ i ] ] ); + } + + return allLanguages; + }; + + /** + * A callback for sorting languages by autonym. + * Can be used as an argument to a sort function. + * + * @param {string} a Language code + * @param {string} b Language code + * @return {number} + */ + $.uls.data.sortByAutonym = function ( a, b ) { + var autonymA = $.uls.data.getAutonym( a ) || a, + autonymB = $.uls.data.getAutonym( b ) || b; + + return ( autonymA.toLowerCase() < autonymB.toLowerCase() ) ? -1 : 1; + }; + + /** + * Check if a language is right-to-left. + * + * @param {string} language Language code + * @return {boolean} + */ + $.uls.data.isRtl = function ( language ) { + return $.uls.data.rtlscripts.indexOf( $.uls.data.getScript( language ) ) !== -1; + }; + + /** + * Return the direction of the language + * + * @param {string} language Language code + * @return {string} + */ + $.uls.data.getDir = function ( language ) { + return $.uls.data.isRtl( language ) ? 'rtl' : 'ltr'; + }; + + /** + * Returns the languages spoken in a territory. + * + * @param {string} territory Territory code + * @return {string[]} list of language codes + */ + $.uls.data.getLanguagesInTerritory = function ( territory ) { + return $.uls.data.territories[ territory ] || []; + }; + + /** + * Adds a language in run time and sets its options as provided. + * If the target option is provided, the language is defined as a redirect. + * Other possible options are script, regions and autonym. + * + * @param {string} code New language code. + * @param {Object} options Language properties. + */ + $.uls.data.addLanguage = function ( code, options ) { + if ( options.target ) { + $.uls.data.languages[ code ] = [ options.target ]; + } else { + $.uls.data.languages[ code ] = [ options.script, options.regions, options.autonym ]; + } + }; + + /** + * Removes a language from the langdb in run time. + * + * @param {string} code Language code to delete. + * @return {boolean} true if the language was removed, false otherwise. + */ + $.uls.data.deleteLanguage = function ( code ) { + if ( $.uls.data.languages[ code ] ) { + delete $.uls.data.languages[ code ]; + + return true; + } + + return false; + }; +}( jQuery ) ); diff --git a/vendor/assets/jquery.uls/src/jquery.uls.languagefilter.js b/vendor/assets/jquery.uls/src/jquery.uls.languagefilter.js new file mode 100644 index 0000000000..c87c680bfe --- /dev/null +++ b/vendor/assets/jquery.uls/src/jquery.uls.languagefilter.js @@ -0,0 +1,361 @@ +/** + * jQuery language filter plugin. + * + * Copyright (C) 2012 Alolita Sharma, Amir Aharoni, Arun Ganesh, Brandon Harris, + * Niklas Laxström, Pau Giner, Santhosh Thottingal, Siebrand Mazeland and other + * contributors. See CREDITS for a list. + * + * UniversalLanguageSelector is dual licensed GPLv2 or later and MIT. You don't + * have to do anything special to choose one license or the other and you don't + * have to notify anyone which license you are using. You are free to use + * UniversalLanguageSelector in commercial projects as long as the copyright + * header is left intact. See files GPL-LICENSE and MIT-LICENSE for details. + * + * @file + * @license GNU General Public Licence 2.0 or later + * @license MIT License + */ + +/** + * Usage: $( 'inputbox' ).languagefilter(); + * The values for autocompletion is from the options.languages or options.searchAPI. + * + * @param {jQuery} $ + */ +( function ( $ ) { + 'use strict'; + + var LanguageFilter; + + /** + * Check if a prefix is visually prefix of a string + * + * @param {string} prefix + * @param {string} string + * @return {boolean} + */ + function isVisualPrefix( prefix, string ) { + // Pre-base vowel signs of Indic languages. A vowel sign is called pre-base if + // consonant + vowel becomes [vowel][consonant] when rendered. Eg: ക + െ => കെ + var prebases = 'െേൈൊോൌெேைொோௌେୈୋୌિਿिিেৈোৌෙේෛොෝෞ'; + return prebases.indexOf( string[ prefix.length ] ) <= 0; + } + + LanguageFilter = function ( element, options ) { + this.$element = $( element ); + this.options = $.extend( {}, $.fn.languagefilter.defaults, options ); + this.$element.addClass( 'languagefilter' ); + this.resultCount = 0; + this.$suggestion = this.$element.siblings( '.' + this.$element.data( 'suggestion' ) ); + this.$clear = this.$element.siblings( '.' + this.$element.data( 'clear' ) ); + this.selectedLanguage = null; + this.init(); + this.listen(); + }; + + LanguageFilter.prototype = { + init: function () { + this.search(); + }, + + listen: function () { + this.$element.on( 'keydown', this.keypress.bind( this ) ); + this.$element.on( 'input', $.fn.uls.debounce( this.onInputChange.bind( this ), 300 ) ); + + if ( this.$clear.length ) { + this.$clear.on( 'click', this.clear.bind( this ) ); + } + + this.toggleClear(); + }, + + onInputChange: function () { + this.selectedLanguage = null; + + if ( !this.$element.val() ) { + this.clear(); + } else { + this.options.lcd.empty(); + this.search(); + } + + this.toggleClear(); + }, + + keypress: function ( e ) { + var suggestion, query; + + switch ( e.keyCode ) { + case 9: // Tab -> Autocomplete + suggestion = this.$suggestion.val(); + + if ( suggestion && suggestion !== this.$element.val() ) { + this.$element.val( suggestion ); + e.preventDefault(); + e.stopPropagation(); + } + break; + case 13: // Enter + if ( !this.options.onSelect ) { + break; + } + + // Avoid bubbling this 'enter' to background page elements + e.preventDefault(); + e.stopPropagation(); + + query = ( this.$element.val() || '' ).trim().toLowerCase(); + + if ( this.selectedLanguage ) { + // this.selectLanguage will be populated from a matching search + this.options.onSelect( this.selectedLanguage, e ); + } else if ( this.options.languages[ query ] ) { + // Search is yet to happen (in timeout delay), + // but we have a matching language code. + this.options.onSelect( query, e ); + } + + break; + } + }, + + /** + * Clears the current search removing + * clear buttons and suggestions. + */ + deactivate: function () { + this.$element.val( '' ); + + if ( !$.fn.uls.Constructor.prototype.isMobile() ) { + this.$element.trigger( 'focus' ); + } + + this.toggleClear(); + this.autofill(); + }, + + /** + * Clears the search and shows all languages + */ + clear: function () { + this.deactivate(); + this.search(); + }, + + /** + * Toggles the visibility of clear icon depending + * on whether there is anything to clear. + */ + toggleClear: function () { + if ( !this.$clear.length ) { + return; + } + + if ( this.$element.val() ) { + this.$clear.show(); + } else { + this.$clear.hide(); + } + }, + + search: function () { + var languages = Object.keys( this.options.languages ), + results = [], + query = ( this.$element.val() || '' ).trim().toLowerCase(); + + if ( query === '' ) { + this.options.lcd.setGroupByRegionOverride( null ); + this.resultHandler( query, languages ); + return; + } + + this.options.lcd.setGroupByRegionOverride( false ); + // Local search results + results = languages.filter( function ( langCode ) { + return this.filter( langCode, query ); + }.bind( this ) ); + + // Use the searchAPI if available, assuming that it has superior search results. + if ( this.options.searchAPI ) { + this.searchAPI( query ) + .done( this.resultHandler.bind( this ) ) + .fail( this.resultHandler.bind( this, query, results, undefined ) ); + } else { + this.resultHandler( query, results ); + } + }, + + searchAPI: function ( query ) { + return $.get( this.options.searchAPI, { search: query } ).then( function ( result ) { + var autofillLabel, + results = []; + + // eslint-disable-next-line no-jquery/no-each-util + $.each( result.languagesearch, function ( apiCode, name ) { + var code, redirect; + + if ( this.options.languages[ apiCode ] ) { + code = apiCode; + } else { + redirect = $.uls.data.isRedirect( apiCode ); + if ( !redirect || !this.options.languages[ redirect ] ) { + return; + } + code = redirect; + } + + // Because of the redirect checking above, we might get duplicates. + // For example if API returns both `sr` and `sr-cyrl`, the former + // could get mapped to `sr-cyrl` and then we would have it twice. + // The exact cases when this happens of course depends on what is in + // options.languages, which might contain redirects such as `sr`. In + // this case we only show `sr` if no other variants are there. + // This also protects against broken search APIs returning duplicate + // results, although that is not happening in practice. + if ( results.indexOf( code ) === -1 ) { + autofillLabel = autofillLabel || name; + results.push( code ); + } + }.bind( this ) ); + + return $.Deferred().resolve( query, results, autofillLabel ); + }.bind( this ) ); + }, + + /** + * Handler method to be called once search is over. + * Based on search result triggers resultsfound or noresults events + * + * @param {string} query + * @param {string[]} results + * @param {string} [autofillLabel] + */ + resultHandler: function ( query, results, autofillLabel ) { + if ( results.length === 0 ) { + this.$suggestion.val( '' ); + this.$element.trigger( + 'noresults.uls', + { + query: query, + ulsPurpose: this.options.ulsPurpose + } + ); + return; + } + + if ( query ) { + this.selectedLanguage = results[ 0 ]; + this.autofill( results[ 0 ], autofillLabel ); + } + + results.map( this.render.bind( this ) ); + this.$element.trigger( 'resultsfound.uls', [ query, results.length ] ); + }, + + autofill: function ( langCode, languageName ) { + var autonym, userInput, suggestion; + + if ( !this.$suggestion.length ) { + return; + } + + if ( !this.$element.val() ) { + this.$suggestion.val( '' ); + return; + } + + languageName = languageName || this.options.languages[ langCode ]; + + if ( !languageName ) { + return; + } + + userInput = this.$element.val(); + suggestion = userInput + + languageName.slice( userInput.length, languageName.length ); + + if ( suggestion.toLowerCase() !== languageName.toLowerCase() ) { + // see if it was autonym match + autonym = $.uls.data.getAutonym( langCode ) || ''; + suggestion = userInput + autonym.slice( userInput.length, autonym.length ); + + if ( suggestion !== autonym ) { + // Give up. It may be an ISO/script code match. + suggestion = ''; + } + } + + // Make sure that it is a visual prefix. + if ( !isVisualPrefix( userInput, suggestion ) ) { + suggestion = ''; + } + + this.$suggestion.val( suggestion ); + }, + + render: function ( langCode ) { + return this.options.lcd.append( langCode ); + }, + + escapeRegex: function ( value ) { + return value.replace( /[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&' ); + }, + + /** + * A search match happens if any of the following passes: + * a) Language name in current user interface language + * 'starts with' search string. + * b) Language autonym 'starts with' search string. + * c) ISO 639 code match with search string. + * d) ISO 15924 code for the script match the search string. + * + * @param {string} langCode + * @param {string} searchTerm + * @return {boolean} + */ + filter: function ( langCode, searchTerm ) { + // FIXME script is ISO 15924 code. We might need actual name of script. + var matcher = new RegExp( '^' + this.escapeRegex( searchTerm ), 'i' ), + languageName = this.options.languages[ langCode ]; + + return matcher.test( languageName ) || + matcher.test( $.uls.data.getAutonym( langCode ) ) || + matcher.test( langCode ) || + matcher.test( $.uls.data.getScript( langCode ) ); + } + }; + + $.fn.languagefilter = function ( option ) { + return this.each( function () { + var $this = $( this ), + data = $this.data( 'languagefilter' ), + options = typeof option === 'object' && option; + + if ( !data ) { + $this.data( 'languagefilter', ( data = new LanguageFilter( this, options ) ) ); + } + + if ( typeof option === 'string' ) { + data[ option ](); + } + } ); + }; + + $.fn.languagefilter.defaults = { + // LanguageCategoryDisplay + lcd: undefined, + // URL to which we append query parameter with the query value + searchAPI: undefined, + // What is this ULS used for. + // Should be set for distinguishing between different instances of ULS + // in the same application. + ulsPurpose: '', + // Object of language tags to language names + languages: [], + // Callback function when language is selected + onSelect: undefined + }; + + $.fn.languagefilter.Constructor = LanguageFilter; + +}( jQuery ) ); diff --git a/vendor/assets/jquery.uls/src/jquery.uls.lcd.js b/vendor/assets/jquery.uls/src/jquery.uls.lcd.js new file mode 100644 index 0000000000..47f082004d --- /dev/null +++ b/vendor/assets/jquery.uls/src/jquery.uls.lcd.js @@ -0,0 +1,481 @@ +/** + * Universal Language Selector + * Language category display component - Used for showing the search results, + * grouped by regions, scripts + * + * Copyright (C) 2012 Alolita Sharma, Amir Aharoni, Arun Ganesh, Brandon Harris, + * Niklas Laxström, Pau Giner, Santhosh Thottingal, Siebrand Mazeland and other + * contributors. See CREDITS for a list. + * + * UniversalLanguageSelector is dual licensed GPLv2 or later and MIT. You don't + * have to do anything special to choose one license or the other and you don't + * have to notify anyone which license you are using. You are free to use + * UniversalLanguageSelector in commercial projects as long as the copyright + * header is left intact. See files GPL-LICENSE and MIT-LICENSE for details. + * + * @file + * @license GNU General Public Licence 2.0 or later + * @license MIT License + */ + +( function ( $ ) { + 'use strict'; + + var noResultsTemplate = '
    \ +

    No results found

    \ +
    \ +
    \ +
    You can search by language name, script name, ISO code of language or you can browse by region.
    \ +
    '; + + /** + * Language category display + * + * @param {Element} element The container element to which the languages to be displayed + * @param {Object} [options] Configuration object + * @cfg {Object} [languages] Selectable languages. Keyed by language code, values are autonyms. + * @cfg {string[]} [showRegions] Array of region codes to show. Default is + * [ 'WW', 'AM', 'EU', 'ME', 'AF', 'AS', 'PA' ] + * @cfg {number} [itemsPerColumn] Number of languages per column. + * @cfg {number} [columns] Number of columns for languages. Default is 4. + * @cfg {Function} [languageDecorator] Callback function to be called when a language + * link is prepared - for custom decoration. + * @cfg {Function|string[]} [quickList] The languages to display as suggestions for quick + * selection. + * @cfg {Function} [clickhandler] Callback when language is selected. + * @cfg {jQuery|Function} [noResultsTemplate] + */ + function LanguageCategoryDisplay( element, options ) { + this.$element = $( element ); + this.options = $.extend( {}, $.fn.lcd.defaults, options ); + // Ensure the internal region 'all' is always present + if ( this.options.showRegions.indexOf( 'all' ) === -1 ) { + this.options.showRegions.push( 'all' ); + } + + this.$element.addClass( 'uls-lcd' ); + this.regionLanguages = {}; + this.renderTimeout = null; + this.$cachedQuicklist = null; + this.groupByRegionOverride = null; + + this.render(); + this.listen(); + } + + LanguageCategoryDisplay.prototype = { + constructor: LanguageCategoryDisplay, + + /** + * Adds language to the language list. + * + * @param {string} langCode + * @param {string} [regionCode] + * @return {boolean} Whether the language was known and accepted + */ + append: function ( langCode, regionCode ) { + var i, regions; + + if ( !$.uls.data.languages[ langCode ] ) { + // Language is unknown or not in the list of languages for this context. + return false; + } + + if ( !this.isGroupingByRegionEnabled() ) { + regions = [ 'all' ]; + + // Make sure we do not get duplicates + if ( this.regionLanguages.all.indexOf( langCode ) > -1 ) { + return true; + } + } else { + if ( regionCode ) { + regions = [ regionCode ]; + } else { + regions = $.uls.data.getRegions( langCode ); + } + } + + for ( i = 0; i < regions.length; i++ ) { + if ( this.regionLanguages[ regions[ i ] ] ) { + this.regionLanguages[ regions[ i ] ].push( langCode ); + } + } + + // Work around the bad interface, delay rendering until we have got + // all the languages to speed up performance. + clearTimeout( this.renderTimeout ); + this.renderTimeout = setTimeout( function () { + this.renderRegions(); + }.bind( this ), 50 ); + + return true; + }, + + /** + * Whether we should render languages grouped to geographic regions. + * + * @return {boolean} + */ + isGroupingByRegionEnabled: function () { + if ( this.groupByRegionOverride !== null ) { + return this.groupByRegionOverride; + } else if ( this.options.groupByRegion !== 'auto' ) { + return this.options.groupByRegion; + } else { + return this.options.columns > 1; + } + }, + + /** + * Override the default region grouping setting. + * This is to allow LanguageFilter to disable grouping when displaying search results. + * + * @param {boolean|null} val True to force grouping, false to disable, null + * to undo override. + */ + setGroupByRegionOverride: function ( val ) { + this.groupByRegionOverride = val; + }, + + render: function () { + var $section, + $quicklist = this.buildQuicklist(), + regions = [], + regionNames = { + // These are fallback text when i18n library not present + all: 'All languages', // Used if there is quicklist and no region grouping + WW: 'Worldwide', + SP: 'Special', + AM: 'America', + EU: 'Europe', + ME: 'Middle East', + AS: 'Asia', + AF: 'Africa', + PA: 'Pacific' + }; + + if ( $quicklist.length ) { + regions.push( $quicklist ); + } else { + // We use CSS to hide the header for 'all' when quicklist is NOT present + this.$element.addClass( 'uls-lcd--no-quicklist' ); + } + + this.options.showRegions.forEach( function ( regionCode ) { + this.regionLanguages[ regionCode ] = []; + + $section = $( '
    ' ) + .addClass( 'uls-lcd-region-section hide' ) + .attr( 'data-region', regionCode ); + + $( '

    ' ) + .attr( 'data-i18n', 'uls-region-' + regionCode ) + .addClass( 'uls-lcd-region-title' ) + .text( regionNames[ regionCode ] ) + .appendTo( $section ); + + regions.push( $section ); + }.bind( this ) ); + + this.$element.append( regions ); + + this.i18n(); + }, + + /** + * Renders a region and displays it if it has content. + */ + renderRegions: function () { + var languages, + lcd = this; + + this.$element.removeClass( 'uls-no-results' ); + this.$element.children( '.uls-lcd-region-section' ).each( function () { + var $region = $( this ), + regionCode = $region.data( 'region' ); + + if ( $region.is( '.uls-lcd-quicklist' ) ) { + return; + } + + $region.children( '.uls-language-block' ).remove(); + + languages = lcd.regionLanguages[ regionCode ]; + if ( !languages || languages.length === 0 ) { + $region.addClass( 'hide' ); + return; + } + + lcd.renderRegion( + $region, + languages, + lcd.options.itemsPerColumn, + lcd.options.columns + ); + $region.removeClass( 'hide' ); + + lcd.regionLanguages[ regionCode ] = []; + } ); + + }, + + /** + * Adds given languages sorted into rows and columns into given element. + * + * @param {jQuery} $region Element to add language list. + * @param {Array} languages List of language codes. + * @param {number} itemsPerColumn How many languages fit in a column. + * @param {number} columnsPerRow How many columns fit in a row. + */ + renderRegion: function ( $region, languages, itemsPerColumn, columnsPerRow ) { + var columnsClasses, i, lastItem, currentScript, nextScript, force, + languagesCount = languages.length, + items = [], + columns = [], + rows = []; + + languages = $.uls.data.sortByScriptGroup( + languages.sort( $.uls.data.sortByAutonym ) + ); + + if ( columnsPerRow === 1 ) { + columnsClasses = 'twelve columns'; + } else if ( columnsPerRow === 2 ) { + columnsClasses = 'six columns'; + } else { + columnsClasses = 'three columns'; + } + + if ( this.options.columns === 1 ) { + // For one-column narrow ULS, just render all the languages + // in one simple list without separators or script groups + for ( i = 0; i < languagesCount; i++ ) { + items.push( this.renderItem( languages[ i ] ) ); + } + + columns.push( $( '
      ' ).addClass( columnsClasses ).append( items ) ); + rows.push( $( '
      ' ).addClass( 'row uls-language-block' ).append( columns ) ); + } else { + // For medium and wide ULS, clever column placement + for ( i = 0; i < languagesCount; i++ ) { + force = false; + nextScript = $.uls.data.getScriptGroupOfLanguage( languages[ i + 1 ] ); + + lastItem = languagesCount - i === 1; + // Force column break if script changes and column has more than one + // row already, but only if grouping by region + if ( i === 0 || !this.isGroupingByRegionEnabled() ) { + currentScript = $.uls.data.getScriptGroupOfLanguage( languages[ i ] ); + } else if ( currentScript !== nextScript && items.length > 1 ) { + force = true; + } + currentScript = nextScript; + + items.push( this.renderItem( languages[ i ] ) ); + + if ( items.length >= itemsPerColumn || lastItem || force ) { + columns.push( $( '
        ' ).addClass( columnsClasses ).append( items ) ); + items = []; + if ( columns.length >= columnsPerRow || lastItem ) { + rows.push( $( '
        ' ).addClass( 'row uls-language-block' ).append( columns ) ); + columns = []; + } + } + } + } + + $region.append( rows ); + }, + + /** + * Creates dom node representing one item in language list. + * + * @param {string} code Language code + * @return {Element} + */ + renderItem: function ( code ) { + var a, name, autonym, li; + + name = this.options.languages[ code ]; + autonym = $.uls.data.getAutonym( code ) || name || code; + + // Not using jQuery as this is performance hotspot + li = document.createElement( 'li' ); + li.title = name; + li.setAttribute( 'data-code', code ); + + a = document.createElement( 'a' ); + a.appendChild( document.createTextNode( autonym ) ); + a.className = 'autonym'; + a.lang = code; + a.dir = $.uls.data.getDir( code ); + + li.appendChild( a ); + if ( this.options.languageDecorator ) { + this.options.languageDecorator( $( a ), code ); + } + return li; + }, + + i18n: function () { + this.$element.find( '[data-i18n]' ).i18n(); + }, + + /** + * Adds quicklist as a region. + */ + quicklist: function () { + this.$element.find( '.uls-lcd-quicklist' ).removeClass( 'hide' ); + }, + + buildQuicklist: function () { + var quickList, $quickListSection, $quickListSectionTitle; + + if ( this.$cachedQuicklist !== null ) { + return this.$cachedQuicklist; + } + + if ( typeof this.options.quickList === 'function' ) { + this.options.quickList = this.options.quickList(); + } + + if ( !this.options.quickList.length ) { + this.$cachedQuicklist = $( [] ); + return this.$cachedQuicklist; + } + + // Pick only the first elements, because we don't have room for more + quickList = this.options.quickList; + quickList = quickList.slice( 0, 16 ); + quickList.sort( $.uls.data.sortByAutonym ); + + $quickListSection = $( '
        ' ) + .addClass( 'uls-lcd-region-section uls-lcd-quicklist' ); + + $quickListSectionTitle = $( '

        ' ) + .attr( 'data-i18n', 'uls-common-languages' ) + .addClass( 'uls-lcd-region-title' ) + .text( 'Suggested languages' ); // This is placeholder text if jquery.i18n not present + $quickListSection.append( $quickListSectionTitle ); + + this.renderRegion( + $quickListSection, + quickList, + this.options.itemsPerColumn, + this.options.columns + ); + + $quickListSectionTitle.i18n(); + + this.$cachedQuicklist = $quickListSection; + return this.$cachedQuicklist; + }, + + show: function () { + if ( !this.regionDivs ) { + this.render(); + } + }, + + /** + * Called when a fresh search is started + */ + empty: function () { + this.$element.addClass( 'uls-lcd--no-quicklist' ); + this.$element.find( '.uls-lcd-quicklist' ).addClass( 'hide' ); + }, + + focus: function () { + this.$element.trigger( 'focus' ); + }, + + /** + * No-results event handler + * + * @param {Event} event + * @param {Object} data Information about the failed search query + */ + noResults: function ( event, data ) { + var $noResults; + + this.$element.addClass( 'uls-no-results' ); + + this.$element.find( '.uls-no-results-view' ).remove(); + + if ( typeof this.options.noResultsTemplate === 'function' ) { + $noResults = + this.options.noResultsTemplate.call( this, data.query ); + } else if ( this.options.noResultsTemplate instanceof jQuery ) { + $noResults = this.options.noResultsTemplate; + } else { + throw new Error( 'noResultsTemplate option must be ' + + 'either jQuery or function returning jQuery' ); + } + + this.$element.append( $noResults.addClass( 'uls-no-results-view' ).i18n() ); + }, + + listen: function () { + var lcd = this; + + if ( this.options.clickhandler ) { + this.$element.on( 'click', '.row li', function ( event ) { + lcd.options.clickhandler.call( this, $( this ).data( 'code' ), event ); + } ); + } + } + }; + + $.fn.lcd = function ( option ) { + return this.each( function () { + var $this = $( this ), + data = $this.data( 'lcd' ), + options = typeof option === 'object' && option; + + if ( !data ) { + $this.data( 'lcd', ( data = new LanguageCategoryDisplay( this, options ) ) ); + } + + if ( typeof option === 'string' ) { + data[ option ](); + } + } ); + }; + + $.fn.lcd.defaults = { + // List of languages to show + languages: [], + // List of regions to show + showRegions: [ 'WW', 'AM', 'EU', 'ME', 'AF', 'AS', 'PA' ], + // Whether to group by region, defaults to true when columns > 1 + groupByRegion: 'auto', + // How many items per column until new "row" starts + itemsPerColumn: 8, + // Number of columns, only 1, 2 and 4 are supported + columns: 4, + // Callback function for language item styling + languageDecorator: undefined, + // Likely candidates + quickList: [], + // Callback function for language selection + clickhandler: undefined, + // Callback function when no search results. + // If overloaded, it can accept the search string as an argument. + noResultsTemplate: function () { + var $suggestionsContainer, $suggestions, + $noResultsTemplate = $( noResultsTemplate ); + + $suggestions = this.buildQuicklist().clone(); + $suggestions.removeClass( 'hide' ) + .find( 'h3' ) + .data( 'i18n', 'uls-no-results-suggestion-title' ) + .text( 'You may be interested in:' ) + .i18n(); + $suggestionsContainer = $noResultsTemplate.find( '.uls-no-results-suggestions' ); + $suggestionsContainer.append( $suggestions ); + return $noResultsTemplate; + } + }; + +}( jQuery ) );