diff --git a/README.md b/README.md index 0265916..7c06e01 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,33 @@ - # 🌐 BCP47Tag ## πŸͺ **Don’t panic. Your tag is valid.** -### Validate, Normalize & Canonicalize BCP 47 Language Tags. That would be `en`, `en-US`, etc ... +### Validate, Normalize & Canonicalize BCP 47 Language Tags (`en`, `en-US`, `zh-Hant-CN`, etc.) ![License](https://img.shields.io/badge/license-MIT-blue.svg) ![PHP](https://img.shields.io/badge/PHP-%3E=8.3-777bb4) -![Tests](https://github.com/lhcze/bcp47-tag/actions/workflows/php.yml/badge.svg) +![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/lhcze/bcp47-tag/php.yml) ![Packagist](https://img.shields.io/packagist/v/lhcze/bcp47-tag) +![Downloads](https://img.shields.io/packagist/dt/lhcze/bcp47-tag) ![IANA Registry](https://img.shields.io/badge/Source-IANA%20Language%20Subtag%20Registry-green) -**BCP47Tag** is a lightweight, robust PHP library for parsing, validating, normalizing, and resolving [BCP 47](https://tools.ietf.org/html/bcp47) language tags β€” the standard that powers `en-US`, `fr-CA`, `zh-Hant-CN`, `i-klingon` (πŸ–– Qapla’!), and more. - ---- - -## βœ… **Why use BCP47Tag?** +**BCP47Tag** is a robust PHP library for working with BCP 47 language tags: -- βœ”οΈ **RFC 5646 / BCP 47 compliant** structure +- βœ”οΈ Validates against the real IANA Language Subtag Registry +- βœ”οΈ ABNF-compliant (RFCβ€―5646) - βœ”οΈ Supports language, script, region, variant, grandfathered tags - βœ”οΈ Auto-normalizes casing & separators (`en_us` β†’ `en-US`) -- βœ”οΈ **Resolves partial language-only tags** (`en` β†’ `en-US`) when you require a canonical tag -- βœ”οΈ Validates against the **official IANA Language Subtag Registry** +- βœ”οΈ Automatically expands collapsed ranges from the registry +- βœ”οΈ Resolves partial language tags (e.g., `en` β†’ `en-US`) using custom canonical matching, with scoring +- βœ”οΈ Error handling via clear exception types +- βœ”οΈ Lightweight `LanguageTag` VO for validated tags +- βœ”οΈ Works perfectly with `ext-intl`β€”no surprises upon feeding ICU - βœ”οΈ Easy fallback mechanism -- βœ”οΈ Zero hidden magic β€” clear, explicit resolution - ️🫧 Supports grandfathered tags so old, they still remember when Unicode 2.0 was hot - πŸ–– Accepts `i-klingon` and `i-enochian` for your occult projects - πŸ€“ `ABNF` so clean, linguists shed a single tear - --- ## ❓ Why not just use `ext-intl`? - -Good question β€” and the answer is: you **should** keep using it! -`BCP47Tag` isn’t here to replace it β€” it exists to **make sure your language tags are clean, canonical, and safe *before* you hand them to ICU**. - -Because we usually rely on **`ext-intl`** for date formats, currencies, or sorting rules β€” and it does that well, *if* the tag is valid. +Good question β€” and the answer is: you **should** keep using it! +`ext-intl` (ICU) is brilliant at formatting *if* your tag is clean. However, it does **not**: @@ -49,6 +44,7 @@ And that’s fine for checking user input β€” but it stops at *structure*. It wo - βœ… Use **BCP47Tag** to *validate & normalize*. - βœ… Hand the cleaned tag to `ext-intl` or whatever else you have for formatting & display. - βœ… Trust you’ll never feed ICU any garbage. +- βœ… Carry around immutable LanguageTag value object across your code base instead of string **BCP47Tag**: RFC 5646 + IANA + real normalization + fallback + resolution. No hustle with regex, `str_replace()` or guesswork. @@ -70,77 +66,93 @@ use LHcze\BCP47\BCP47Tag; // Just normalize & validate $tag = new BCP47Tag('en_us'); -echo $tag->getNormalized(); // en-US +echo $tag->getNormalized(); // "en-US" +echo $tag->getICUformat(); // "en_US" + +// With canonical matching +$tag = new BCP47Tag('en', useCanonicalMatchTags: ['de-DE', 'en-US']); +echo $tag->getNormalized(); // "en-US" // Use fallback if invalid -$tag = new BCP47Tag('notreal', fallbackLocale: 'fr-FR'); +$tag = new BCP47Tag('notreal', 'fr-FR'); echo $tag->getNormalized(); // fr-FR -// Resolve partial to known canonical tag -$tag = new BCP47Tag( - 'en', - knownTags: ['en-US', 'en-GB'], - requireCanonical: true -); -echo $tag->getNormalized(); // en-US +// Invalid input β†’ exception +try { + new BCP47Tag('invalid!!'); +} catch (BCP47InvalidLocaleException $e) { + echo $e->getMessage(); +} // Feed to ext-intl $icu = $tag->getICULocale(); // en_US echo Locale::getDisplayLanguage($icu); // English -// Inspect parsed parts (language, script, region, variants) -$parsed = $tag->getParsedTag(); -echo $parsed?->getLanguage(); // en -echo $parsed?->getRegion(); // US +// LanguageTag VO +$langTag = $tag->getLanguageTag(); +echo $langTag->getLanguage(); // "en" +echo $langTag->getRegion(); // "US" +echo (string) $langTag; // "en-US" ``` --- -## πŸ” **How Resolution Works** +## πŸ” **Features & Flow** -βœ… **`knownTags`** -Provide an explicit list of canonical BCP 47 tags your app accepts. -If the input is partial (like `en`), the tag will resolve to the first matching known tag (`en-US`). Position in the list is a priority. +1. **Normalize + parse** + Clean casing/formatting and parse into components. -βœ… **`requireCanonical`** -When true, language-only input must resolve to a canonical known tag β€” or the constructor will throw an `InvalidArgumentException`. +2. **Validate against IANA** + Broken input or fallback triggers explicit exceptions: + - `BCP47InvalidLocaleException` + - `BCP47InvalidFallbackLocaleException` -βœ… **Fallback** -If the input is invalid and a fallback is provided, it will be used instead. +3. **Canonical matching (optional)** + - Pass an array of `useCanonicalMatchTags` + - Each is matched and scored: + +100 language match, +10 region, +1 script + - Highest score wins. + - Same score makes the first one to have it to make a home run ---- +4. **LanguageTag VO** + Immutable, validated, `Stringable` & `JsonSerializable`. -## 🌐 **Powered by Official IANA Data** +--- +## πŸ“œ Supported Tags BCP47Tag uses a **precompiled static PHP snapshot** of the latest **IANA Language Subtag Registry** to validate languages, scripts, regions, variants, and grandfathered tags. - The registry is loaded **once per process**, kept hot in OPcache for maximum speed. +- βœ… ISO language, script, region, variants +- βœ… Grandfathered/deprecated tags (e.g., `i-klingon`) +- βœ… Collapsed registry ranges are auto-expanded +- ⚠️ Extensions & private-use subtags (future) --- ## 🧩 **Key API** -| Method | Purpose | -|-------------------|----------------------------------------------| -| `getNormalized()` | RFC 5646 standard `xx-XX` format | -| `getICULocale()` | `xx_XX` format safe for `ext-intl` | -| `getOriginalInput()` | Raw input string | -| `getParsedTag()` | Returns the ParsedTag value object for advanced inspection | -| `__toString()` | Returns the normalized tag | +| Method | Description | +|--------|-------------| +| `__construct(string $input, ?string $fallback, ?array $useCanonicalMatchTags)` | Main entry | +| `getInputLocale()` | Original input string | +| `getNormalized()` | RFC‑5646 formatted tag | +| `getICUformat()` | Underscore variant (`xx_XX`) | +| `getLanguageTag()` | Returns `LanguageTag` VO | +| `__toString()` / `jsonSerialize()` | Returns normalized string | --- ## πŸ“œ The Official BCP 47 ABNF -The syntax your tags must follow is defined by [RFC 5646](https://datatracker.ietf.org/doc/html/rfc5646) in ABNF: +The syntax tags must follow is defined by [RFC 5646](https://datatracker.ietf.org/doc/html/rfc5646) in ABNF: ```abnf langtag = language - ["-" script] - ["-" region] - *("-" variant) - *("-" extension) - ["-" privateuse] + ["-" script] + ["-" region] + *("-" variant) + *("-" extension) + ["-" privateuse] ``` Examples: @@ -153,6 +165,15 @@ Examples: BCP47Tag respects this ABNF, so your tags match the real spec β€” no hidden assumptions. +--- +## ❓ **Why is this useful?** + +Use cases include: +- Validating API `Accept-Language` headers +- Multi-regional CMS deployments +- Internationalization pipelines +- Locale-dependent services where mis-typed tags lead to silent failures + --- ## βš™οΈ **Requirements** @@ -165,34 +186,35 @@ BCP47Tag respects this ABNF, so your tags match the real spec β€” no hidden assu ## πŸ§ͺ **Tests** ```bash -vendor/bin/phpunit +composer qa ``` --- ## πŸ“Œ **Roadmap** +- βœ… IANA Language Subtag Registry integration - βœ… Language, script, region, variant validation -- βœ… IANA subtag registry integration -- βœ… Canonical resolution with known tags +- βœ… Lazy singleton registry loader - βœ… Static PHP snapshot of the IANA registry for ultra-fast lookups -- βœ… Lazy singleton registry loader for low memory overhead -- βš™οΈ Extensions & private-use subtags (planned) -- βš™οΈ Automatic periodic IANA registry updates (planned) -- βš™οΈ CLI tool to refresh the IANA data easily -- βš™οΈ Optional Symfony service for container-based caching +- βœ… Canonical matching with scoring +- βœ… Typed exceptions for flow control +- βš™οΈ Extension/subtag support (planned) +- βš™οΈ Additional data use from IANA registry (suppress-script subtag, preferred, prefix) +- βš™οΈ Auto-registry refresh script --- -## πŸ“– **License** + +## πŸ“– License [MIT](LICENSE) --- -## πŸ”— **References** +## πŸ”— References -- [BCP 47 Specification (RFC 5646)](https://tools.ietf.org/html/rfc5646) +- [RFC 5646 – BCPβ€―47 ABNF](https://tools.ietf.org/html/rfc5646) - [IANA Language Subtag Registry](https://www.iana.org/assignments/language-subtag-registry) --- diff --git a/bin/fetch_iana_registry.php b/bin/fetch_iana_registry.php index 5e0f7b8..44a8693 100644 --- a/bin/fetch_iana_registry.php +++ b/bin/fetch_iana_registry.php @@ -1,7 +1,23 @@ > + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * TODO + * Swap array index keys for actual tag/subtag for faster lookups + * That would also allow adding a tag/subtag additional data such as preferred-Value, prefix, description, etc. + * + */ declare(strict_types=1); +require __DIR__ . '/../src/Utils/expandAlphaRangeFunction.php';; + /** * This script downloads the IANA Language Subtag Registry and converts it to a PHP file * with a return statement for faster loading in the IanaSubtagRegistry class. @@ -10,8 +26,9 @@ // URL of the IANA Language Subtag Registry $registryUrl = 'https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry'; -// Output file path -$outputFile = __DIR__ . '/../src/Resources/IanaSubtagRegistry.php'; +// Output file paths +$outputFile = __DIR__ . '/../src/Resources/IanaSubtagRegistryResource.php'; +$enumOutputFile = __DIR__ . '/../src/Enum/GrandfatheredTag.php'; echo "Downloading IANA Language Subtag Registry...\n"; @@ -19,7 +36,11 @@ $ch = curl_init($registryUrl); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); -curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'); +curl_setopt( + $ch, + CURLOPT_USERAGENT, + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', +); curl_setopt($ch, CURLOPT_TIMEOUT, 30); $registryContent = curl_exec($ch); @@ -32,7 +53,7 @@ exit(1); } -echo "Parsing registry data...\n"; +echo sprintf("Parsing registry data... size (Kb): %s\n", round(strlen($registryContent) / 1024, 2)); // Initialize arrays to store different types of subtags $languages = []; @@ -44,7 +65,7 @@ // Split the registry into sections by '%%' $sections = explode('%%', $registryContent); -// Process each section +/// Process each section foreach ($sections as $section) { // Skip empty sections if (trim($section) === '') { @@ -83,35 +104,45 @@ $data[$currentKey] = trim($currentValue); } - // Categorize the section based on its Type - if (isset($data['Type'])) { - switch ($data['Type']) { - case 'language': - if (isset($data['Subtag'])) { - $languages[] = strtolower($data['Subtag']); - } - break; - case 'script': - if (isset($data['Subtag'])) { - $scripts[] = ucfirst(strtolower($data['Subtag'])); - } - break; - case 'region': - if (isset($data['Subtag'])) { - $regions[] = strtoupper($data['Subtag']); - } - break; - case 'variant': - if (isset($data['Subtag'])) { - $variants[] = strtolower($data['Subtag']); - } - break; - case 'grandfathered': - if (isset($data['Tag'])) { - $grandfathered[] = strtolower($data['Tag']); - } - break; + // Check if this section is a valid subtag entry + if (!isset($data['Type'])) { + continue; + } + + $type = $data['Type']; + + // Handle grandfathered tags separately + if ($type === 'grandfathered' && isset($data['Tag'])) { + $grandfathered[] = strtolower($data['Tag']); + continue; + } + + // Handle entries with a Subtag + if (!isset($data['Subtag'])) { + continue; + } + + $subtag = $data['Subtag']; + + // If the subtag is a range (e.g., 'Qaaa..Qabx'), expand it ('qaa..qtz' β†’ ['qaa', 'qab', ..., 'qtz']) + if (str_contains($subtag, '..')) { + [$start, $end] = explode('..', $subtag); + + switch ($type) { + case 'language': expandAlphaRangeFunction($languages, $start, $end, fn($s) => strtolower($s)); break; + case 'script': expandAlphaRangeFunction($scripts, $start, $end, fn($s) => ucfirst(strtolower($s))); break; + case 'region': expandAlphaRangeFunction($regions, $start, $end, fn($s) => strtoupper($s)); break; } + + continue; + } + + // Regular single subtag entry + switch ($type) { + case 'language': $languages[] = strtolower($subtag); break; + case 'script': $scripts[] = ucfirst(strtolower($subtag)); break; + case 'region': $regions[] = strtoupper($subtag); break; + case 'variant': $variants[] = strtolower($subtag); break; } } @@ -131,11 +162,22 @@ 'grandfathered' => array_values(array_unique($grandfathered)), ]; +$data = var_export($data, true); +if ($data === null) { + $data = [ + 'languages' => [], + 'scripts' => [], + 'regions' => [], + 'variants' => [], + 'grandfathered' => [], + ]; +} + // Generate PHP file content $phpContent = "=8.1" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.6-dev" - } - }, - "autoload": { - "files": [ - "function.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "A generic function and convention to trigger deprecation notices", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-25T14:21:43+00:00" - }, - { - "name": "symfony/intl", - "version": "v6.4.23", - "source": { - "type": "git", - "url": "https://github.com/symfony/intl.git", - "reference": "a3b12ce29a770d1f26c380dabf56a71bc2a88c84" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/intl/zipball/a3b12ce29a770d1f26c380dabf56a71bc2a88c84", - "reference": "a3b12ce29a770d1f26c380dabf56a71bc2a88c84", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "symfony/filesystem": "^5.4|^6.0|^7.0", - "symfony/finder": "^5.4|^6.0|^7.0", - "symfony/var-exporter": "^5.4|^6.0|^7.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Intl\\": "" - }, - "exclude-from-classmap": [ - "/Tests/", - "/Resources/data/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - }, - { - "name": "Eriksen Costa", - "email": "eriksen.costa@infranology.com.br" - }, - { - "name": "Igor Wiedler", - "email": "igor@wiedler.ch" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides access to the localization data of the ICU library", - "homepage": "https://symfony.com", - "keywords": [ - "i18n", - "icu", - "internationalization", - "intl", - "l10n", - "localization" - ], - "support": { - "source": "https://github.com/symfony/intl/tree/v6.4.23" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-06-06T07:42:46+00:00" - }, - { - "name": "symfony/polyfill-ctype", - "version": "v1.32.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", - "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "provide": { - "ext-ctype": "*" - }, - "suggest": { - "ext-ctype": "For best performance" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for ctype functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" - ], - "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-09T11:45:10+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.32.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", - "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", - "shasum": "" - }, - "require": { - "ext-iconv": "*", - "php": ">=7.2" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-12-23T08:48:59+00:00" - }, - { - "name": "symfony/polyfill-php83", - "version": "v1.32.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/2fb86d65e2d424369ad2905e83b236a8805ba491", - "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php83\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.32.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-09T11:45:10+00:00" - }, - { - "name": "symfony/translation-contracts", - "version": "v3.6.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/translation-contracts.git", - "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/df210c7a2573f1913b2d17cc95f90f53a73d8f7d", - "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.6-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\Translation\\": "" - }, - "exclude-from-classmap": [ - "/Test/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to translation", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.6.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-27T08:32:26+00:00" - }, - { - "name": "symfony/validator", - "version": "v6.4.23", - "source": { - "type": "git", - "url": "https://github.com/symfony/validator.git", - "reference": "6506760ab57e7cda5bde9cdaed736526162284bc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/validator/zipball/6506760ab57e7cda5bde9cdaed736526162284bc", - "reference": "6506760ab57e7cda5bde9cdaed736526162284bc", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php83": "^1.27", - "symfony/translation-contracts": "^2.5|^3" - }, - "conflict": { - "doctrine/annotations": "<1.13", - "doctrine/lexer": "<1.1", - "symfony/dependency-injection": "<5.4", - "symfony/expression-language": "<5.4", - "symfony/http-kernel": "<5.4", - "symfony/intl": "<5.4", - "symfony/property-info": "<5.4", - "symfony/translation": "<5.4.35|>=6.0,<6.3.12|>=6.4,<6.4.3|>=7.0,<7.0.3", - "symfony/yaml": "<5.4" - }, - "require-dev": { - "doctrine/annotations": "^1.13|^2", - "egulias/email-validator": "^2.1.10|^3|^4", - "symfony/cache": "^5.4|^6.0|^7.0", - "symfony/config": "^5.4|^6.0|^7.0", - "symfony/console": "^5.4|^6.0|^7.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/expression-language": "^5.4|^6.0|^7.0", - "symfony/finder": "^5.4|^6.0|^7.0", - "symfony/http-client": "^5.4|^6.0|^7.0", - "symfony/http-foundation": "^5.4|^6.0|^7.0", - "symfony/http-kernel": "^5.4|^6.0|^7.0", - "symfony/intl": "^5.4|^6.0|^7.0", - "symfony/mime": "^5.4|^6.0|^7.0", - "symfony/property-access": "^5.4|^6.0|^7.0", - "symfony/property-info": "^5.4|^6.0|^7.0", - "symfony/translation": "^5.4.35|~6.3.12|^6.4.3|^7.0.3", - "symfony/yaml": "^5.4|^6.0|^7.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Validator\\": "" - }, - "exclude-from-classmap": [ - "/Tests/", - "/Resources/bin/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides tools to validate values", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/validator/tree/v6.4.23" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-06-26T07:25:45+00:00" - } - ], + "content-hash": "95a4880dc06075ff14ba2f29f9cb23b5", + "packages": [], "packages-dev": [ { "name": "clue/ndjson-react", @@ -1684,37 +1122,85 @@ ], "type": "library", "autoload": { - "files": [ - "bootstrap.php" - ] + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "time": "2025-05-21T20:55:28+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "3e139cbe67fafa3588e1dbe27ca50f31fdb6236a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/3e139cbe67fafa3588e1dbe27ca50f31fdb6236a", + "reference": "3e139cbe67fafa3588e1dbe27ca50f31fdb6236a", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.0.4" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "PHPStan - PHP Static Analysis Tool", - "keywords": [ - "dev", - "static analysis" - ], + "description": "Extra strict and opinionated rules for PHPStan", "support": { - "docs": "https://phpstan.org/user-guide/getting-started", - "forum": "https://github.com/phpstan/phpstan/discussions", - "issues": "https://github.com/phpstan/phpstan/issues", - "security": "https://github.com/phpstan/phpstan/security/policy", - "source": "https://github.com/phpstan/phpstan-src" + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.4" }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - } - ], - "time": "2025-05-21T20:55:28+00:00" + "time": "2025-03-18T11:42:40+00:00" }, { "name": "phpunit/php-code-coverage", @@ -4200,6 +3686,73 @@ ], "time": "2025-06-24T04:04:43+00:00" }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, { "name": "symfony/event-dispatcher", "version": "v7.3.0", @@ -4553,6 +4106,85 @@ ], "time": "2025-04-04T13:12:05+00:00" }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, { "name": "symfony/polyfill-intl-grapheme", "version": "v1.32.0", @@ -4712,6 +4344,87 @@ ], "time": "2024-09-09T11:45:10+00:00" }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-23T08:48:59+00:00" + }, { "name": "symfony/polyfill-php80", "version": "v1.32.0", diff --git a/docs/worklog.md b/docs/worklog.md index 0ae346f..8e9978d 100644 --- a/docs/worklog.md +++ b/docs/worklog.md @@ -259,3 +259,30 @@ These fixes improve the code quality and type safety of the library, making it m - Code quality checks passed These optimizations improve performance by eliminating JSON parsing overhead and implementing a lazy loading pattern that loads the registry only once per request. The removal of symfony dependencies makes the library more lightweight and self-contained. + +## 10. 2025-07-17: Refactor BCP47Tag library to give better results, understandable interface, exception and performace + +1. Refactored BCP47Tag class, changed class construction signature. + - Replaced $knownTags and $requireCanonical arguments with $useCanonicalMatchTags array + - Refactored the internals during construction of the object to better handle fallback, normalization and validation + (including fallback and useCanonicalMatchTags array) + - added public interface getLanguageTag(): LanguageTag method so you can work with even easier value object in your + code base, + - useCanonicalMatchTags is now using scoring system to match the resolved language tag to provided list +2. ParsedTag value object is now Stringable and JsonSerializable +3. Introducing LanguageTagβ€” an immutable value object that can be created only via it's own factory method and extending + ParsedTag so it's also stringable and json serializable +4. BCP47Normalizer now uses auto-generated GrandfatheredTag enum to detect grandfathered tags +5. Few modifications to BCP47Parser to more readable, throw exceptions, and handle grandfathered tags correctly +6. Few modifications to IanaSubtagRegistry to throw exceptions, added TODO list what is missing and made code more + readable +7. Resource file is now static php array instead of json file +8. Resource file compilation how expands range tags into individual tags/subtags, uses utility function + expandAlphaRangeFunction() +9. Made Exception classes with a logical flow in case something goes wrong +10. Added phpstan/phpstan-strict-rules into composer +11. Added `composer iana:refresh` to fetch and compile fresh source of data +12. Some more... + +This version if far more ready to work great on production environments for 99.8% of the cases that might be needed :) +While I still see the joy to work towards the goal to make the 0.2% happy as well (flex) \ No newline at end of file diff --git a/qatools/phpmd_ruleset.xml b/qatools/phpmd_ruleset.xml index 4acd6ae..343939f 100644 --- a/qatools/phpmd_ruleset.xml +++ b/qatools/phpmd_ruleset.xml @@ -83,6 +83,7 @@ + diff --git a/qatools/phpstan.neon b/qatools/phpstan.neon new file mode 100644 index 0000000..ffb18e1 --- /dev/null +++ b/qatools/phpstan.neon @@ -0,0 +1,4 @@ +parameters: + level: max + paths: + - ../src \ No newline at end of file diff --git a/resources/iana.json b/resources/iana.json deleted file mode 100644 index 365e042..0000000 --- a/resources/iana.json +++ /dev/null @@ -1,8964 +0,0 @@ -{ - "languages": [ - "aa", - "aaa", - "aab", - "aac", - "aad", - "aae", - "aaf", - "aag", - "aah", - "aai", - "aak", - "aal", - "aam", - "aan", - "aao", - "aap", - "aaq", - "aas", - "aat", - "aau", - "aav", - "aaw", - "aax", - "aaz", - "ab", - "aba", - "abb", - "abc", - "abd", - "abe", - "abf", - "abg", - "abh", - "abi", - "abj", - "abl", - "abm", - "abn", - "abo", - "abp", - "abq", - "abr", - "abs", - "abt", - "abu", - "abv", - "abw", - "abx", - "aby", - "abz", - "aca", - "acb", - "acd", - "ace", - "acf", - "ach", - "aci", - "ack", - "acl", - "acm", - "acn", - "acp", - "acq", - "acr", - "acs", - "act", - "acu", - "acv", - "acw", - "acx", - "acy", - "acz", - "ada", - "adb", - "add", - "ade", - "adf", - "adg", - "adh", - "adi", - "adj", - "adl", - "adn", - "ado", - "adp", - "adq", - "adr", - "ads", - "adt", - "adu", - "adw", - "adx", - "ady", - "adz", - "ae", - "aea", - "aeb", - "aec", - "aed", - "aee", - "aek", - "ael", - "aem", - "aen", - "aeq", - "aer", - "aes", - "aeu", - "aew", - "aey", - "aez", - "af", - "afa", - "afb", - "afd", - "afe", - "afg", - "afh", - "afi", - "afk", - "afn", - "afo", - "afp", - "afs", - "aft", - "afu", - "afz", - "aga", - "agb", - "agc", - "agd", - "age", - "agf", - "agg", - "agh", - "agi", - "agj", - "agk", - "agl", - "agm", - "agn", - "ago", - "agp", - "agq", - "agr", - "ags", - "agt", - "agu", - "agv", - "agw", - "agx", - "agy", - "agz", - "aha", - "ahb", - "ahg", - "ahh", - "ahi", - "ahk", - "ahl", - "ahm", - "ahn", - "aho", - "ahp", - "ahr", - "ahs", - "aht", - "aia", - "aib", - "aic", - "aid", - "aie", - "aif", - "aig", - "aih", - "aii", - "aij", - "aik", - "ail", - "aim", - "ain", - "aio", - "aip", - "aiq", - "air", - "ais", - "ait", - "aiw", - "aix", - "aiy", - "aja", - "ajg", - "aji", - "ajn", - "ajp", - "ajs", - "ajt", - "aju", - "ajw", - "ajz", - "ak", - "akb", - "akc", - "akd", - "ake", - "akf", - "akg", - "akh", - "aki", - "akj", - "akk", - "akl", - "akm", - "ako", - "akp", - "akq", - "akr", - "aks", - "akt", - "aku", - "akv", - "akw", - "akx", - "aky", - "akz", - "ala", - "alc", - "ald", - "ale", - "alf", - "alg", - "alh", - "ali", - "alj", - "alk", - "all", - "alm", - "aln", - "alo", - "alp", - "alq", - "alr", - "als", - "alt", - "alu", - "alv", - "alw", - "alx", - "aly", - "alz", - "am", - "ama", - "amb", - "amc", - "ame", - "amf", - "amg", - "ami", - "amj", - "amk", - "aml", - "amm", - "amn", - "amo", - "amp", - "amq", - "amr", - "ams", - "amt", - "amu", - "amv", - "amw", - "amx", - "amy", - "amz", - "an", - "ana", - "anb", - "anc", - "and", - "ane", - "anf", - "ang", - "anh", - "ani", - "anj", - "ank", - "anl", - "anm", - "ann", - "ano", - "anp", - "anq", - "anr", - "ans", - "ant", - "anu", - "anv", - "anw", - "anx", - "any", - "anz", - "aoa", - "aob", - "aoc", - "aod", - "aoe", - "aof", - "aog", - "aoh", - "aoi", - "aoj", - "aok", - "aol", - "aom", - "aon", - "aor", - "aos", - "aot", - "aou", - "aox", - "aoz", - "apa", - "apb", - "apc", - "apd", - "ape", - "apf", - "apg", - "aph", - "api", - "apj", - "apk", - "apl", - "apm", - "apn", - "apo", - "app", - "apq", - "apr", - "aps", - "apt", - "apu", - "apv", - "apw", - "apx", - "apy", - "apz", - "aqa", - "aqc", - "aqd", - "aqg", - "aqk", - "aql", - "aqm", - "aqn", - "aqp", - "aqr", - "aqt", - "aqz", - "ar", - "arb", - "arc", - "ard", - "are", - "arh", - "ari", - "arj", - "ark", - "arl", - "arn", - "aro", - "arp", - "arq", - "arr", - "ars", - "art", - "aru", - "arv", - "arw", - "arx", - "ary", - "arz", - "as", - "asa", - "asb", - "asc", - "asd", - "ase", - "asf", - "asg", - "ash", - "asi", - "asj", - "ask", - "asl", - "asn", - "aso", - "asp", - "asq", - "asr", - "ass", - "ast", - "asu", - "asv", - "asw", - "asx", - "asy", - "asz", - "ata", - "atb", - "atc", - "atd", - "ate", - "atg", - "ath", - "ati", - "atj", - "atk", - "atl", - "atm", - "atn", - "ato", - "atp", - "atq", - "atr", - "ats", - "att", - "atu", - "atv", - "atw", - "atx", - "aty", - "atz", - "aua", - "aub", - "auc", - "aud", - "aue", - "auf", - "aug", - "auh", - "aui", - "auj", - "auk", - "aul", - "aum", - "aun", - "auo", - "aup", - "auq", - "aur", - "aus", - "aut", - "auu", - "auw", - "aux", - "auy", - "auz", - "av", - "avb", - "avd", - "avi", - "avk", - "avl", - "avm", - "avn", - "avo", - "avs", - "avt", - "avu", - "avv", - "awa", - "awb", - "awc", - "awd", - "awe", - "awg", - "awh", - "awi", - "awk", - "awm", - "awn", - "awo", - "awr", - "aws", - "awt", - "awu", - "awv", - "aww", - "awx", - "awy", - "axb", - "axe", - "axg", - "axk", - "axl", - "axm", - "axx", - "ay", - "aya", - "ayb", - "ayc", - "ayd", - "aye", - "ayg", - "ayh", - "ayi", - "ayk", - "ayl", - "ayn", - "ayo", - "ayp", - "ayq", - "ayr", - "ays", - "ayt", - "ayu", - "ayx", - "ayy", - "ayz", - "az", - "aza", - "azb", - "azc", - "azd", - "azg", - "azj", - "azm", - "azn", - "azo", - "azt", - "azz", - "ba", - "baa", - "bab", - "bac", - "bad", - "bae", - "baf", - "bag", - "bah", - "bai", - "baj", - "bal", - "ban", - "bao", - "bap", - "bar", - "bas", - "bat", - "bau", - "bav", - "baw", - "bax", - "bay", - "baz", - "bba", - "bbb", - "bbc", - "bbd", - "bbe", - "bbf", - "bbg", - "bbh", - "bbi", - "bbj", - "bbk", - "bbl", - "bbm", - "bbn", - "bbo", - "bbp", - "bbq", - "bbr", - "bbs", - "bbt", - "bbu", - "bbv", - "bbw", - "bbx", - "bby", - "bbz", - "bca", - "bcb", - "bcc", - "bcd", - "bce", - "bcf", - "bcg", - "bch", - "bci", - "bcj", - "bck", - "bcl", - "bcm", - "bcn", - "bco", - "bcp", - "bcq", - "bcr", - "bcs", - "bct", - "bcu", - "bcv", - "bcw", - "bcy", - "bcz", - "bda", - "bdb", - "bdc", - "bdd", - "bde", - "bdf", - "bdg", - "bdh", - "bdi", - "bdj", - "bdk", - "bdl", - "bdm", - "bdn", - "bdo", - "bdp", - "bdq", - "bdr", - "bds", - "bdt", - "bdu", - "bdv", - "bdw", - "bdx", - "bdy", - "bdz", - "be", - "bea", - "beb", - "bec", - "bed", - "bee", - "bef", - "beg", - "beh", - "bei", - "bej", - "bek", - "bem", - "beo", - "bep", - "beq", - "ber", - "bes", - "bet", - "beu", - "bev", - "bew", - "bex", - "bey", - "bez", - "bfa", - "bfb", - "bfc", - "bfd", - "bfe", - "bff", - "bfg", - "bfh", - "bfi", - "bfj", - "bfk", - "bfl", - "bfm", - "bfn", - "bfo", - "bfp", - "bfq", - "bfr", - "bfs", - "bft", - "bfu", - "bfw", - "bfx", - "bfy", - "bfz", - "bg", - "bga", - "bgb", - "bgc", - "bgd", - "bge", - "bgf", - "bgg", - "bgi", - "bgj", - "bgk", - "bgl", - "bgm", - "bgn", - "bgo", - "bgp", - "bgq", - "bgr", - "bgs", - "bgt", - "bgu", - "bgv", - "bgw", - "bgx", - "bgy", - "bgz", - "bh", - "bha", - "bhb", - "bhc", - "bhd", - "bhe", - "bhf", - "bhg", - "bhh", - "bhi", - "bhj", - "bhk", - "bhl", - "bhm", - "bhn", - "bho", - "bhp", - "bhq", - "bhr", - "bhs", - "bht", - "bhu", - "bhv", - "bhw", - "bhx", - "bhy", - "bhz", - "bi", - "bia", - "bib", - "bic", - "bid", - "bie", - "bif", - "big", - "bij", - "bik", - "bil", - "bim", - "bin", - "bio", - "bip", - "biq", - "bir", - "bit", - "biu", - "biv", - "biw", - "bix", - "biy", - "biz", - "bja", - "bjb", - "bjc", - "bjd", - "bje", - "bjf", - "bjg", - "bjh", - "bji", - "bjj", - "bjk", - "bjl", - "bjm", - "bjn", - "bjo", - "bjp", - "bjq", - "bjr", - "bjs", - "bjt", - "bju", - "bjv", - "bjw", - "bjx", - "bjy", - "bjz", - "bka", - "bkb", - "bkc", - "bkd", - "bkf", - "bkg", - "bkh", - "bki", - "bkj", - "bkk", - "bkl", - "bkm", - "bkn", - "bko", - "bkp", - "bkq", - "bkr", - "bks", - "bkt", - "bku", - "bkv", - "bkw", - "bkx", - "bky", - "bkz", - "bla", - "blb", - "blc", - "bld", - "ble", - "blf", - "blg", - "blh", - "bli", - "blj", - "blk", - "bll", - "blm", - "bln", - "blo", - "blp", - "blq", - "blr", - "bls", - "blt", - "blv", - "blw", - "blx", - "bly", - "blz", - "bm", - "bma", - "bmb", - "bmc", - "bmd", - "bme", - "bmf", - "bmg", - "bmh", - "bmi", - "bmj", - "bmk", - "bml", - "bmm", - "bmn", - "bmo", - "bmp", - "bmq", - "bmr", - "bms", - "bmt", - "bmu", - "bmv", - "bmw", - "bmx", - "bmy", - "bmz", - "bn", - "bna", - "bnb", - "bnc", - "bnd", - "bne", - "bnf", - "bng", - "bni", - "bnj", - "bnk", - "bnl", - "bnm", - "bnn", - "bno", - "bnp", - "bnq", - "bnr", - "bns", - "bnt", - "bnu", - "bnv", - "bnw", - "bnx", - "bny", - "bnz", - "bo", - "boa", - "bob", - "boe", - "bof", - "bog", - "boh", - "boi", - "boj", - "bok", - "bol", - "bom", - "bon", - "boo", - "bop", - "boq", - "bor", - "bot", - "bou", - "bov", - "bow", - "box", - "boy", - "boz", - "bpa", - "bpb", - "bpc", - "bpd", - "bpe", - "bpg", - "bph", - "bpi", - "bpj", - "bpk", - "bpl", - "bpm", - "bpn", - "bpo", - "bpp", - "bpq", - "bpr", - "bps", - "bpt", - "bpu", - "bpv", - "bpw", - "bpx", - "bpy", - "bpz", - "bqa", - "bqb", - "bqc", - "bqd", - "bqf", - "bqg", - "bqh", - "bqi", - "bqj", - "bqk", - "bql", - "bqm", - "bqn", - "bqo", - "bqp", - "bqq", - "bqr", - "bqs", - "bqt", - "bqu", - "bqv", - "bqw", - "bqx", - "bqy", - "bqz", - "br", - "bra", - "brb", - "brc", - "brd", - "brf", - "brg", - "brh", - "bri", - "brj", - "brk", - "brl", - "brm", - "brn", - "bro", - "brp", - "brq", - "brr", - "brs", - "brt", - "bru", - "brv", - "brw", - "brx", - "bry", - "brz", - "bs", - "bsa", - "bsb", - "bsc", - "bse", - "bsf", - "bsg", - "bsh", - "bsi", - "bsj", - "bsk", - "bsl", - "bsm", - "bsn", - "bso", - "bsp", - "bsq", - "bsr", - "bss", - "bst", - "bsu", - "bsv", - "bsw", - "bsx", - "bsy", - "bta", - "btb", - "btc", - "btd", - "bte", - "btf", - "btg", - "bth", - "bti", - "btj", - "btk", - "btl", - "btm", - "btn", - "bto", - "btp", - "btq", - "btr", - "bts", - "btt", - "btu", - "btv", - "btw", - "btx", - "bty", - "btz", - "bua", - "bub", - "buc", - "bud", - "bue", - "buf", - "bug", - "buh", - "bui", - "buj", - "buk", - "bum", - "bun", - "buo", - "bup", - "buq", - "bus", - "but", - "buu", - "buv", - "buw", - "bux", - "buy", - "buz", - "bva", - "bvb", - "bvc", - "bvd", - "bve", - "bvf", - "bvg", - "bvh", - "bvi", - "bvj", - "bvk", - "bvl", - "bvm", - "bvn", - "bvo", - "bvp", - "bvq", - "bvr", - "bvt", - "bvu", - "bvv", - "bvw", - "bvx", - "bvy", - "bvz", - "bwa", - "bwb", - "bwc", - "bwd", - "bwe", - "bwf", - "bwg", - "bwh", - "bwi", - "bwj", - "bwk", - "bwl", - "bwm", - "bwn", - "bwo", - "bwp", - "bwq", - "bwr", - "bws", - "bwt", - "bwu", - "bww", - "bwx", - "bwy", - "bwz", - "bxa", - "bxb", - "bxc", - "bxd", - "bxe", - "bxf", - "bxg", - "bxh", - "bxi", - "bxj", - "bxk", - "bxl", - "bxm", - "bxn", - "bxo", - "bxp", - "bxq", - "bxr", - "bxs", - "bxu", - "bxv", - "bxw", - "bxx", - "bxz", - "bya", - "byb", - "byc", - "byd", - "bye", - "byf", - "byg", - "byh", - "byi", - "byj", - "byk", - "byl", - "bym", - "byn", - "byo", - "byp", - "byq", - "byr", - "bys", - "byt", - "byv", - "byw", - "byx", - "byy", - "byz", - "bza", - "bzb", - "bzc", - "bzd", - "bze", - "bzf", - "bzg", - "bzh", - "bzi", - "bzj", - "bzk", - "bzl", - "bzm", - "bzn", - "bzo", - "bzp", - "bzq", - "bzr", - "bzs", - "bzt", - "bzu", - "bzv", - "bzw", - "bzx", - "bzy", - "bzz", - "ca", - "caa", - "cab", - "cac", - "cad", - "cae", - "caf", - "cag", - "cah", - "cai", - "caj", - "cak", - "cal", - "cam", - "can", - "cao", - "cap", - "caq", - "car", - "cas", - "cau", - "cav", - "caw", - "cax", - "cay", - "caz", - "cba", - "cbb", - "cbc", - "cbd", - "cbe", - "cbg", - "cbh", - "cbi", - "cbj", - "cbk", - "cbl", - "cbn", - "cbo", - "cbq", - "cbr", - "cbs", - "cbt", - "cbu", - "cbv", - "cbw", - "cby", - "cca", - "ccc", - "ccd", - "cce", - "ccg", - "cch", - "ccj", - "ccl", - "ccm", - "ccn", - "cco", - "ccp", - "ccq", - "ccr", - "ccs", - "cda", - "cdc", - "cdd", - "cde", - "cdf", - "cdg", - "cdh", - "cdi", - "cdj", - "cdm", - "cdn", - "cdo", - "cdr", - "cds", - "cdy", - "cdz", - "ce", - "cea", - "ceb", - "ceg", - "cek", - "cel", - "cen", - "cet", - "cey", - "cfa", - "cfd", - "cfg", - "cfm", - "cga", - "cgc", - "cgg", - "cgk", - "ch", - "chb", - "chc", - "chd", - "chf", - "chg", - "chh", - "chj", - "chk", - "chl", - "chm", - "chn", - "cho", - "chp", - "chq", - "chr", - "cht", - "chw", - "chx", - "chy", - "chz", - "cia", - "cib", - "cic", - "cid", - "cie", - "cih", - "cik", - "cim", - "cin", - "cip", - "cir", - "ciw", - "ciy", - "cja", - "cje", - "cjh", - "cji", - "cjk", - "cjm", - "cjn", - "cjo", - "cjp", - "cjr", - "cjs", - "cjv", - "cjy", - "cka", - "ckb", - "ckh", - "ckl", - "ckm", - "ckn", - "cko", - "ckq", - "ckr", - "cks", - "ckt", - "cku", - "ckv", - "ckx", - "cky", - "ckz", - "cla", - "clc", - "cld", - "cle", - "clh", - "cli", - "clj", - "clk", - "cll", - "clm", - "clo", - "cls", - "clt", - "clu", - "clw", - "cly", - "cma", - "cmc", - "cme", - "cmg", - "cmi", - "cmk", - "cml", - "cmm", - "cmn", - "cmo", - "cmr", - "cms", - "cmt", - "cna", - "cnb", - "cnc", - "cng", - "cnh", - "cni", - "cnk", - "cnl", - "cno", - "cnp", - "cnq", - "cnr", - "cns", - "cnt", - "cnu", - "cnw", - "cnx", - "co", - "coa", - "cob", - "coc", - "cod", - "coe", - "cof", - "cog", - "coh", - "coj", - "cok", - "col", - "com", - "con", - "coo", - "cop", - "coq", - "cot", - "cou", - "cov", - "cow", - "cox", - "coy", - "coz", - "cpa", - "cpb", - "cpc", - "cpe", - "cpf", - "cpg", - "cpi", - "cpn", - "cpo", - "cpp", - "cps", - "cpu", - "cpx", - "cpy", - "cqd", - "cqu", - "cr", - "cra", - "crb", - "crc", - "crd", - "crf", - "crg", - "crh", - "cri", - "crj", - "crk", - "crl", - "crm", - "crn", - "cro", - "crp", - "crq", - "crr", - "crs", - "crt", - "crv", - "crw", - "crx", - "cry", - "crz", - "cs", - "csa", - "csb", - "csc", - "csd", - "cse", - "csf", - "csg", - "csh", - "csi", - "csj", - "csk", - "csl", - "csm", - "csn", - "cso", - "csp", - "csq", - "csr", - "css", - "cst", - "csu", - "csv", - "csw", - "csx", - "csy", - "csz", - "cta", - "ctc", - "ctd", - "cte", - "ctg", - "cth", - "ctl", - "ctm", - "ctn", - "cto", - "ctp", - "cts", - "ctt", - "ctu", - "cty", - "ctz", - "cu", - "cua", - "cub", - "cuc", - "cug", - "cuh", - "cui", - "cuj", - "cuk", - "cul", - "cum", - "cuo", - "cup", - "cuq", - "cur", - "cus", - "cut", - "cuu", - "cuv", - "cuw", - "cux", - "cuy", - "cv", - "cvg", - "cvn", - "cwa", - "cwb", - "cwd", - "cwe", - "cwg", - "cwt", - "cxh", - "cy", - "cya", - "cyb", - "cyo", - "czh", - "czk", - "czn", - "czo", - "czt", - "da", - "daa", - "dac", - "dad", - "dae", - "daf", - "dag", - "dah", - "dai", - "daj", - "dak", - "dal", - "dam", - "dao", - "dap", - "daq", - "dar", - "das", - "dau", - "dav", - "daw", - "dax", - "day", - "daz", - "dba", - "dbb", - "dbd", - "dbe", - "dbf", - "dbg", - "dbi", - "dbj", - "dbl", - "dbm", - "dbn", - "dbo", - "dbp", - "dbq", - "dbr", - "dbt", - "dbu", - "dbv", - "dbw", - "dby", - "dcc", - "dcr", - "dda", - "ddd", - "dde", - "ddg", - "ddi", - "ddj", - "ddn", - "ddo", - "ddr", - "dds", - "ddw", - "de", - "dec", - "ded", - "dee", - "def", - "deg", - "deh", - "dei", - "dek", - "del", - "dem", - "den", - "dep", - "deq", - "der", - "des", - "dev", - "dez", - "dga", - "dgb", - "dgc", - "dgd", - "dge", - "dgg", - "dgh", - "dgi", - "dgk", - "dgl", - "dgn", - "dgo", - "dgr", - "dgs", - "dgt", - "dgu", - "dgw", - "dgx", - "dgz", - "dha", - "dhd", - "dhg", - "dhi", - "dhl", - "dhm", - "dhn", - "dho", - "dhr", - "dhs", - "dhu", - "dhv", - "dhw", - "dhx", - "dia", - "dib", - "dic", - "did", - "dif", - "dig", - "dih", - "dii", - "dij", - "dik", - "dil", - "dim", - "din", - "dio", - "dip", - "diq", - "dir", - "dis", - "dit", - "diu", - "diw", - "dix", - "diy", - "diz", - "dja", - "djb", - "djc", - "djd", - "dje", - "djf", - "dji", - "djj", - "djk", - "djl", - "djm", - "djn", - "djo", - "djr", - "dju", - "djw", - "dka", - "dkg", - "dkk", - "dkl", - "dkr", - "dks", - "dkx", - "dlg", - "dlk", - "dlm", - "dln", - "dma", - "dmb", - "dmc", - "dmd", - "dme", - "dmf", - "dmg", - "dmk", - "dml", - "dmm", - "dmn", - "dmo", - "dmr", - "dms", - "dmu", - "dmv", - "dmw", - "dmx", - "dmy", - "dna", - "dnd", - "dne", - "dng", - "dni", - "dnj", - "dnk", - "dnn", - "dno", - "dnr", - "dnt", - "dnu", - "dnv", - "dnw", - "dny", - "doa", - "dob", - "doc", - "doe", - "dof", - "doh", - "doi", - "dok", - "dol", - "don", - "doo", - "dop", - "doq", - "dor", - "dos", - "dot", - "dov", - "dow", - "dox", - "doy", - "doz", - "dpp", - "dra", - "drb", - "drc", - "drd", - "dre", - "drg", - "drh", - "dri", - "drl", - "drn", - "dro", - "drq", - "drr", - "drs", - "drt", - "dru", - "drw", - "dry", - "dsb", - "dse", - "dsh", - "dsi", - "dsk", - "dsl", - "dsn", - "dso", - "dsq", - "dsz", - "dta", - "dtb", - "dtd", - "dth", - "dti", - "dtk", - "dtm", - "dtn", - "dto", - "dtp", - "dtr", - "dts", - "dtt", - "dtu", - "dty", - "dua", - "dub", - "duc", - "dud", - "due", - "duf", - "dug", - "duh", - "dui", - "duj", - "duk", - "dul", - "dum", - "dun", - "duo", - "dup", - "duq", - "dur", - "dus", - "duu", - "duv", - "duw", - "dux", - "duy", - "duz", - "dv", - "dva", - "dwa", - "dwk", - "dwl", - "dwr", - "dws", - "dwu", - "dww", - "dwy", - "dwz", - "dya", - "dyb", - "dyd", - "dyg", - "dyi", - "dym", - "dyn", - "dyo", - "dyr", - "dyu", - "dyy", - "dz", - "dza", - "dzd", - "dze", - "dzg", - "dzl", - "dzn", - "eaa", - "ebc", - "ebg", - "ebk", - "ebo", - "ebr", - "ebu", - "ecr", - "ecs", - "ecy", - "ee", - "eee", - "efa", - "efe", - "efi", - "ega", - "egl", - "egm", - "ego", - "egx", - "egy", - "ehs", - "ehu", - "eip", - "eit", - "eiv", - "eja", - "eka", - "ekc", - "eke", - "ekg", - "eki", - "ekk", - "ekl", - "ekm", - "eko", - "ekp", - "ekr", - "eky", - "el", - "ele", - "elh", - "eli", - "elk", - "elm", - "elo", - "elp", - "elu", - "elx", - "ema", - "emb", - "eme", - "emg", - "emi", - "emk", - "emm", - "emn", - "emo", - "emp", - "emq", - "ems", - "emu", - "emw", - "emx", - "emy", - "emz", - "en", - "ena", - "enb", - "enc", - "end", - "enf", - "enh", - "enl", - "enm", - "enn", - "eno", - "enq", - "enr", - "enu", - "env", - "enw", - "enx", - "eo", - "eot", - "epi", - "era", - "erg", - "erh", - "eri", - "erk", - "ero", - "err", - "ers", - "ert", - "erw", - "es", - "ese", - "esg", - "esh", - "esi", - "esk", - "esl", - "esm", - "esn", - "eso", - "esq", - "ess", - "esu", - "esx", - "esy", - "et", - "etb", - "etc", - "eth", - "etn", - "eto", - "etr", - "ets", - "ett", - "etu", - "etx", - "etz", - "eu", - "eud", - "euq", - "eve", - "evh", - "evn", - "ewo", - "ext", - "eya", - "eyo", - "eza", - "eze", - "fa", - "faa", - "fab", - "fad", - "faf", - "fag", - "fah", - "fai", - "faj", - "fak", - "fal", - "fam", - "fan", - "fap", - "far", - "fat", - "fau", - "fax", - "fay", - "faz", - "fbl", - "fcs", - "fer", - "ff", - "ffi", - "ffm", - "fgr", - "fi", - "fia", - "fie", - "fif", - "fil", - "fip", - "fir", - "fit", - "fiu", - "fiw", - "fj", - "fkk", - "fkv", - "fla", - "flh", - "fli", - "fll", - "fln", - "flr", - "fly", - "fmp", - "fmu", - "fnb", - "fng", - "fni", - "fo", - "fod", - "foi", - "fom", - "fon", - "for", - "fos", - "fox", - "fpe", - "fqs", - "fr", - "frc", - "frd", - "frk", - "frm", - "fro", - "frp", - "frq", - "frr", - "frs", - "frt", - "fse", - "fsl", - "fss", - "fub", - "fuc", - "fud", - "fue", - "fuf", - "fuh", - "fui", - "fuj", - "fum", - "fun", - "fuq", - "fur", - "fut", - "fuu", - "fuv", - "fuy", - "fvr", - "fwa", - "fwe", - "fy", - "ga", - "gaa", - "gab", - "gac", - "gad", - "gae", - "gaf", - "gag", - "gah", - "gai", - "gaj", - "gak", - "gal", - "gam", - "gan", - "gao", - "gap", - "gaq", - "gar", - "gas", - "gat", - "gau", - "gav", - "gaw", - "gax", - "gay", - "gaz", - "gba", - "gbb", - "gbc", - "gbd", - "gbe", - "gbf", - "gbg", - "gbh", - "gbi", - "gbj", - "gbk", - "gbl", - "gbm", - "gbn", - "gbo", - "gbp", - "gbq", - "gbr", - "gbs", - "gbu", - "gbv", - "gbw", - "gbx", - "gby", - "gbz", - "gcc", - "gcd", - "gce", - "gcf", - "gcl", - "gcn", - "gcr", - "gct", - "gd", - "gda", - "gdb", - "gdc", - "gdd", - "gde", - "gdf", - "gdg", - "gdh", - "gdi", - "gdj", - "gdk", - "gdl", - "gdm", - "gdn", - "gdo", - "gdq", - "gdr", - "gds", - "gdt", - "gdu", - "gdx", - "gea", - "geb", - "gec", - "ged", - "gef", - "geg", - "geh", - "gei", - "gej", - "gek", - "gel", - "gem", - "geq", - "ges", - "gev", - "gew", - "gex", - "gey", - "gez", - "gfk", - "gft", - "gfx", - "gga", - "ggb", - "ggd", - "gge", - "ggg", - "ggk", - "ggl", - "ggn", - "ggo", - "ggr", - "ggt", - "ggu", - "ggw", - "gha", - "ghc", - "ghe", - "ghh", - "ghk", - "ghl", - "ghn", - "gho", - "ghr", - "ghs", - "ght", - "gia", - "gib", - "gic", - "gid", - "gie", - "gig", - "gih", - "gii", - "gil", - "gim", - "gin", - "gio", - "gip", - "giq", - "gir", - "gis", - "git", - "giu", - "giw", - "gix", - "giy", - "giz", - "gji", - "gjk", - "gjm", - "gjn", - "gjr", - "gju", - "gka", - "gkd", - "gke", - "gkn", - "gko", - "gkp", - "gku", - "gl", - "glb", - "glc", - "gld", - "glh", - "gli", - "glj", - "glk", - "gll", - "glo", - "glr", - "glu", - "glw", - "gly", - "gma", - "gmb", - "gmd", - "gme", - "gmg", - "gmh", - "gml", - "gmm", - "gmn", - "gmq", - "gmr", - "gmu", - "gmv", - "gmw", - "gmx", - "gmy", - "gmz", - "gn", - "gna", - "gnb", - "gnc", - "gnd", - "gne", - "gng", - "gnh", - "gni", - "gnj", - "gnk", - "gnl", - "gnm", - "gnn", - "gno", - "gnq", - "gnr", - "gnt", - "gnu", - "gnw", - "gnz", - "goa", - "gob", - "goc", - "god", - "goe", - "gof", - "gog", - "goh", - "goi", - "goj", - "gok", - "gol", - "gom", - "gon", - "goo", - "gop", - "goq", - "gor", - "gos", - "got", - "gou", - "gov", - "gow", - "gox", - "goy", - "goz", - "gpa", - "gpe", - "gpn", - "gqa", - "gqi", - "gqn", - "gqr", - "gqu", - "gra", - "grb", - "grc", - "grd", - "grg", - "grh", - "gri", - "grj", - "grk", - "grm", - "gro", - "grq", - "grr", - "grs", - "grt", - "gru", - "grv", - "grw", - "grx", - "gry", - "grz", - "gse", - "gsg", - "gsl", - "gsm", - "gsn", - "gso", - "gsp", - "gss", - "gsw", - "gta", - "gti", - "gtu", - "gu", - "gua", - "gub", - "guc", - "gud", - "gue", - "guf", - "gug", - "guh", - "gui", - "guk", - "gul", - "gum", - "gun", - "guo", - "gup", - "guq", - "gur", - "gus", - "gut", - "guu", - "guv", - "guw", - "gux", - "guz", - "gv", - "gva", - "gvc", - "gve", - "gvf", - "gvj", - "gvl", - "gvm", - "gvn", - "gvo", - "gvp", - "gvr", - "gvs", - "gvy", - "gwa", - "gwb", - "gwc", - "gwd", - "gwe", - "gwf", - "gwg", - "gwi", - "gwj", - "gwm", - "gwn", - "gwr", - "gwt", - "gwu", - "gww", - "gwx", - "gxx", - "gya", - "gyb", - "gyd", - "gye", - "gyf", - "gyg", - "gyi", - "gyl", - "gym", - "gyn", - "gyo", - "gyr", - "gyy", - "gyz", - "gza", - "gzi", - "gzn", - "ha", - "haa", - "hab", - "hac", - "had", - "hae", - "haf", - "hag", - "hah", - "hai", - "haj", - "hak", - "hal", - "ham", - "han", - "hao", - "hap", - "haq", - "har", - "has", - "hav", - "haw", - "hax", - "hay", - "haz", - "hba", - "hbb", - "hbn", - "hbo", - "hbu", - "hca", - "hch", - "hdn", - "hds", - "hdy", - "he", - "hea", - "hed", - "heg", - "heh", - "hei", - "hem", - "hgm", - "hgw", - "hhi", - "hhr", - "hhy", - "hi", - "hia", - "hib", - "hid", - "hif", - "hig", - "hih", - "hii", - "hij", - "hik", - "hil", - "him", - "hio", - "hir", - "hit", - "hiw", - "hix", - "hji", - "hka", - "hke", - "hkh", - "hkk", - "hkn", - "hks", - "hla", - "hlb", - "hld", - "hle", - "hlt", - "hlu", - "hma", - "hmb", - "hmc", - "hmd", - "hme", - "hmf", - "hmg", - "hmh", - "hmi", - "hmj", - "hmk", - "hml", - "hmm", - "hmn", - "hmp", - "hmq", - "hmr", - "hms", - "hmt", - "hmu", - "hmv", - "hmw", - "hmx", - "hmy", - "hmz", - "hna", - "hnd", - "hne", - "hng", - "hnh", - "hni", - "hnj", - "hnm", - "hnn", - "hno", - "hns", - "hnu", - "ho", - "hoa", - "hob", - "hoc", - "hod", - "hoe", - "hoh", - "hoi", - "hoj", - "hok", - "hol", - "hom", - "hoo", - "hop", - "hor", - "hos", - "hot", - "hov", - "how", - "hoy", - "hoz", - "hpo", - "hps", - "hr", - "hra", - "hrc", - "hre", - "hrk", - "hrm", - "hro", - "hrp", - "hrr", - "hrt", - "hru", - "hrw", - "hrx", - "hrz", - "hsb", - "hsh", - "hsl", - "hsn", - "hss", - "ht", - "hti", - "hto", - "hts", - "htu", - "htx", - "hu", - "hub", - "huc", - "hud", - "hue", - "huf", - "hug", - "huh", - "hui", - "huj", - "huk", - "hul", - "hum", - "huo", - "hup", - "huq", - "hur", - "hus", - "hut", - "huu", - "huv", - "huw", - "hux", - "huy", - "huz", - "hvc", - "hve", - "hvk", - "hvn", - "hvv", - "hwa", - "hwc", - "hwo", - "hy", - "hya", - "hyw", - "hyx", - "hz", - "ia", - "iai", - "ian", - "iap", - "iar", - "iba", - "ibb", - "ibd", - "ibe", - "ibg", - "ibh", - "ibi", - "ibl", - "ibm", - "ibn", - "ibr", - "ibu", - "iby", - "ica", - "ich", - "icl", - "icr", - "id", - "ida", - "idb", - "idc", - "idd", - "ide", - "idi", - "idr", - "ids", - "idt", - "idu", - "ie", - "ifa", - "ifb", - "ife", - "iff", - "ifk", - "ifm", - "ifu", - "ify", - "ig", - "igb", - "ige", - "igg", - "igl", - "igm", - "ign", - "igo", - "igs", - "igw", - "ihb", - "ihi", - "ihp", - "ihw", - "ii", - "iin", - "iir", - "ijc", - "ije", - "ijj", - "ijn", - "ijo", - "ijs", - "ik", - "ike", - "ikh", - "iki", - "ikk", - "ikl", - "iko", - "ikp", - "ikr", - "iks", - "ikt", - "ikv", - "ikw", - "ikx", - "ikz", - "ila", - "ilb", - "ilg", - "ili", - "ilk", - "ill", - "ilm", - "ilo", - "ilp", - "ils", - "ilu", - "ilv", - "ilw", - "ima", - "ime", - "imi", - "iml", - "imn", - "imo", - "imr", - "ims", - "imt", - "imy", - "in", - "inb", - "inc", - "ine", - "ing", - "inh", - "inj", - "inl", - "inm", - "inn", - "ino", - "inp", - "ins", - "int", - "inz", - "io", - "ior", - "iou", - "iow", - "ipi", - "ipo", - "iqu", - "iqw", - "ira", - "ire", - "irh", - "iri", - "irk", - "irn", - "iro", - "irr", - "iru", - "irx", - "iry", - "is", - "isa", - "isc", - "isd", - "ise", - "isg", - "ish", - "isi", - "isk", - "ism", - "isn", - "iso", - "isr", - "ist", - "isu", - "isv", - "it", - "itb", - "itc", - "itd", - "ite", - "iti", - "itk", - "itl", - "itm", - "ito", - "itr", - "its", - "itt", - "itv", - "itw", - "itx", - "ity", - "itz", - "iu", - "ium", - "ivb", - "ivv", - "iw", - "iwk", - "iwm", - "iwo", - "iws", - "ixc", - "ixl", - "iya", - "iyo", - "iyx", - "izh", - "izi", - "izm", - "izr", - "izz", - "ja", - "jaa", - "jab", - "jac", - "jad", - "jae", - "jaf", - "jah", - "jaj", - "jak", - "jal", - "jam", - "jan", - "jao", - "jaq", - "jar", - "jas", - "jat", - "jau", - "jax", - "jay", - "jaz", - "jbe", - "jbi", - "jbj", - "jbk", - "jbm", - "jbn", - "jbo", - "jbr", - "jbt", - "jbu", - "jbw", - "jcs", - "jct", - "jda", - "jdg", - "jdt", - "jeb", - "jee", - "jeg", - "jeh", - "jei", - "jek", - "jel", - "jen", - "jer", - "jet", - "jeu", - "jgb", - "jge", - "jgk", - "jgo", - "jhi", - "jhs", - "ji", - "jia", - "jib", - "jic", - "jid", - "jie", - "jig", - "jih", - "jii", - "jil", - "jim", - "jio", - "jiq", - "jit", - "jiu", - "jiv", - "jiy", - "jje", - "jjr", - "jka", - "jkm", - "jko", - "jkp", - "jkr", - "jks", - "jku", - "jle", - "jls", - "jma", - "jmb", - "jmc", - "jmd", - "jmi", - "jml", - "jmn", - "jmr", - "jms", - "jmw", - "jmx", - "jna", - "jnd", - "jng", - "jni", - "jnj", - "jnl", - "jns", - "job", - "jod", - "jog", - "jor", - "jos", - "jow", - "jpa", - "jpr", - "jpx", - "jqr", - "jra", - "jrb", - "jrr", - "jrt", - "jru", - "jsl", - "jua", - "jub", - "juc", - "jud", - "juh", - "jui", - "juk", - "jul", - "jum", - "jun", - "juo", - "jup", - "jur", - "jus", - "jut", - "juu", - "juw", - "juy", - "jv", - "jvd", - "jvn", - "jw", - "jwi", - "jya", - "jye", - "jyy", - "ka", - "kaa", - "kab", - "kac", - "kad", - "kae", - "kaf", - "kag", - "kah", - "kai", - "kaj", - "kak", - "kam", - "kao", - "kap", - "kaq", - "kar", - "kav", - "kaw", - "kax", - "kay", - "kba", - "kbb", - "kbc", - "kbd", - "kbe", - "kbf", - "kbg", - "kbh", - "kbi", - "kbj", - "kbk", - "kbl", - "kbm", - "kbn", - "kbo", - "kbp", - "kbq", - "kbr", - "kbs", - "kbt", - "kbu", - "kbv", - "kbw", - "kbx", - "kby", - "kbz", - "kca", - "kcb", - "kcc", - "kcd", - "kce", - "kcf", - "kcg", - "kch", - "kci", - "kcj", - "kck", - "kcl", - "kcm", - "kcn", - "kco", - "kcp", - "kcq", - "kcr", - "kcs", - "kct", - "kcu", - "kcv", - "kcw", - "kcx", - "kcy", - "kcz", - "kda", - "kdc", - "kdd", - "kde", - "kdf", - "kdg", - "kdh", - "kdi", - "kdj", - "kdk", - "kdl", - "kdm", - "kdn", - "kdo", - "kdp", - "kdq", - "kdr", - "kdt", - "kdu", - "kdv", - "kdw", - "kdx", - "kdy", - "kdz", - "kea", - "keb", - "kec", - "ked", - "kee", - "kef", - "keg", - "keh", - "kei", - "kej", - "kek", - "kel", - "kem", - "ken", - "keo", - "kep", - "keq", - "ker", - "kes", - "ket", - "keu", - "kev", - "kew", - "kex", - "key", - "kez", - "kfa", - "kfb", - "kfc", - "kfd", - "kfe", - "kff", - "kfg", - "kfh", - "kfi", - "kfj", - "kfk", - "kfl", - "kfm", - "kfn", - "kfo", - "kfp", - "kfq", - "kfr", - "kfs", - "kft", - "kfu", - "kfv", - "kfw", - "kfx", - "kfy", - "kfz", - "kg", - "kga", - "kgb", - "kgc", - "kgd", - "kge", - "kgf", - "kgg", - "kgh", - "kgi", - "kgj", - "kgk", - "kgl", - "kgm", - "kgn", - "kgo", - "kgp", - "kgq", - "kgr", - "kgs", - "kgt", - "kgu", - "kgv", - "kgw", - "kgx", - "kgy", - "kha", - "khb", - "khc", - "khd", - "khe", - "khf", - "khg", - "khh", - "khi", - "khj", - "khk", - "khl", - "khn", - "kho", - "khp", - "khq", - "khr", - "khs", - "kht", - "khu", - "khv", - "khw", - "khx", - "khy", - "khz", - "ki", - "kia", - "kib", - "kic", - "kid", - "kie", - "kif", - "kig", - "kih", - "kii", - "kij", - "kil", - "kim", - "kio", - "kip", - "kiq", - "kis", - "kit", - "kiu", - "kiv", - "kiw", - "kix", - "kiy", - "kiz", - "kj", - "kja", - "kjb", - "kjc", - "kjd", - "kje", - "kjf", - "kjg", - "kjh", - "kji", - "kjj", - "kjk", - "kjl", - "kjm", - "kjn", - "kjo", - "kjp", - "kjq", - "kjr", - "kjs", - "kjt", - "kju", - "kjv", - "kjx", - "kjy", - "kjz", - "kk", - "kka", - "kkb", - "kkc", - "kkd", - "kke", - "kkf", - "kkg", - "kkh", - "kki", - "kkj", - "kkk", - "kkl", - "kkm", - "kkn", - "kko", - "kkp", - "kkq", - "kkr", - "kks", - "kkt", - "kku", - "kkv", - "kkw", - "kkx", - "kky", - "kkz", - "kl", - "kla", - "klb", - "klc", - "kld", - "kle", - "klf", - "klg", - "klh", - "kli", - "klj", - "klk", - "kll", - "klm", - "kln", - "klo", - "klp", - "klq", - "klr", - "kls", - "klt", - "klu", - "klv", - "klw", - "klx", - "kly", - "klz", - "km", - "kma", - "kmb", - "kmc", - "kmd", - "kme", - "kmf", - "kmg", - "kmh", - "kmi", - "kmj", - "kmk", - "kml", - "kmm", - "kmn", - "kmo", - "kmp", - "kmq", - "kmr", - "kms", - "kmt", - "kmu", - "kmv", - "kmw", - "kmx", - "kmy", - "kmz", - "kn", - "kna", - "knb", - "knc", - "knd", - "kne", - "knf", - "kng", - "kni", - "knj", - "knk", - "knl", - "knm", - "knn", - "kno", - "knp", - "knq", - "knr", - "kns", - "knt", - "knu", - "knv", - "knw", - "knx", - "kny", - "knz", - "ko", - "koa", - "koc", - "kod", - "koe", - "kof", - "kog", - "koh", - "koi", - "koj", - "kok", - "kol", - "koo", - "kop", - "koq", - "kos", - "kot", - "kou", - "kov", - "kow", - "kox", - "koy", - "koz", - "kpa", - "kpb", - "kpc", - "kpd", - "kpe", - "kpf", - "kpg", - "kph", - "kpi", - "kpj", - "kpk", - "kpl", - "kpm", - "kpn", - "kpo", - "kpp", - "kpq", - "kpr", - "kps", - "kpt", - "kpu", - "kpv", - "kpw", - "kpx", - "kpy", - "kpz", - "kqa", - "kqb", - "kqc", - "kqd", - "kqe", - "kqf", - "kqg", - "kqh", - "kqi", - "kqj", - "kqk", - "kql", - "kqm", - "kqn", - "kqo", - "kqp", - "kqq", - "kqr", - "kqs", - "kqt", - "kqu", - "kqv", - "kqw", - "kqx", - "kqy", - "kqz", - "kr", - "kra", - "krb", - "krc", - "krd", - "kre", - "krf", - "krh", - "kri", - "krj", - "krk", - "krl", - "krm", - "krn", - "kro", - "krp", - "krr", - "krs", - "krt", - "kru", - "krv", - "krw", - "krx", - "kry", - "krz", - "ks", - "ksa", - "ksb", - "ksc", - "ksd", - "kse", - "ksf", - "ksg", - "ksh", - "ksi", - "ksj", - "ksk", - "ksl", - "ksm", - "ksn", - "kso", - "ksp", - "ksq", - "ksr", - "kss", - "kst", - "ksu", - "ksv", - "ksw", - "ksx", - "ksy", - "ksz", - "kta", - "ktb", - "ktc", - "ktd", - "kte", - "ktf", - "ktg", - "kth", - "kti", - "ktj", - "ktk", - "ktl", - "ktm", - "ktn", - "kto", - "ktp", - "ktq", - "ktr", - "kts", - "ktt", - "ktu", - "ktv", - "ktw", - "ktx", - "kty", - "ktz", - "ku", - "kub", - "kuc", - "kud", - "kue", - "kuf", - "kug", - "kuh", - "kui", - "kuj", - "kuk", - "kul", - "kum", - "kun", - "kuo", - "kup", - "kuq", - "kus", - "kut", - "kuu", - "kuv", - "kuw", - "kux", - "kuy", - "kuz", - "kv", - "kva", - "kvb", - "kvc", - "kvd", - "kve", - "kvf", - "kvg", - "kvh", - "kvi", - "kvj", - "kvk", - "kvl", - "kvm", - "kvn", - "kvo", - "kvp", - "kvq", - "kvr", - "kvs", - "kvt", - "kvu", - "kvv", - "kvw", - "kvx", - "kvy", - "kvz", - "kw", - "kwa", - "kwb", - "kwc", - "kwd", - "kwe", - "kwf", - "kwg", - "kwh", - "kwi", - "kwj", - "kwk", - "kwl", - "kwm", - "kwn", - "kwo", - "kwp", - "kwq", - "kwr", - "kws", - "kwt", - "kwu", - "kwv", - "kww", - "kwx", - "kwy", - "kwz", - "kxa", - "kxb", - "kxc", - "kxd", - "kxe", - "kxf", - "kxh", - "kxi", - "kxj", - "kxk", - "kxl", - "kxm", - "kxn", - "kxo", - "kxp", - "kxq", - "kxr", - "kxs", - "kxt", - "kxu", - "kxv", - "kxw", - "kxx", - "kxy", - "kxz", - "ky", - "kya", - "kyb", - "kyc", - "kyd", - "kye", - "kyf", - "kyg", - "kyh", - "kyi", - "kyj", - "kyk", - "kyl", - "kym", - "kyn", - "kyo", - "kyp", - "kyq", - "kyr", - "kys", - "kyt", - "kyu", - "kyv", - "kyw", - "kyx", - "kyy", - "kyz", - "kza", - "kzb", - "kzc", - "kzd", - "kze", - "kzf", - "kzg", - "kzh", - "kzi", - "kzj", - "kzk", - "kzl", - "kzm", - "kzn", - "kzo", - "kzp", - "kzq", - "kzr", - "kzs", - "kzt", - "kzu", - "kzv", - "kzw", - "kzx", - "kzy", - "kzz", - "la", - "laa", - "lab", - "lac", - "lad", - "lae", - "laf", - "lag", - "lah", - "lai", - "laj", - "lak", - "lal", - "lam", - "lan", - "lap", - "laq", - "lar", - "las", - "lau", - "law", - "lax", - "lay", - "laz", - "lb", - "lba", - "lbb", - "lbc", - "lbe", - "lbf", - "lbg", - "lbi", - "lbj", - "lbk", - "lbl", - "lbm", - "lbn", - "lbo", - "lbq", - "lbr", - "lbs", - "lbt", - "lbu", - "lbv", - "lbw", - "lbx", - "lby", - "lbz", - "lcc", - "lcd", - "lce", - "lcf", - "lch", - "lcl", - "lcm", - "lcp", - "lcq", - "lcs", - "lda", - "ldb", - "ldd", - "ldg", - "ldh", - "ldi", - "ldj", - "ldk", - "ldl", - "ldm", - "ldn", - "ldo", - "ldp", - "ldq", - "lea", - "leb", - "lec", - "led", - "lee", - "lef", - "leg", - "leh", - "lei", - "lej", - "lek", - "lel", - "lem", - "len", - "leo", - "lep", - "leq", - "ler", - "les", - "let", - "leu", - "lev", - "lew", - "lex", - "ley", - "lez", - "lfa", - "lfn", - "lg", - "lga", - "lgb", - "lgg", - "lgh", - "lgi", - "lgk", - "lgl", - "lgm", - "lgn", - "lgo", - "lgq", - "lgr", - "lgs", - "lgt", - "lgu", - "lgz", - "lha", - "lhh", - "lhi", - "lhl", - "lhm", - "lhn", - "lhp", - "lhs", - "lht", - "lhu", - "li", - "lia", - "lib", - "lic", - "lid", - "lie", - "lif", - "lig", - "lih", - "lii", - "lij", - "lik", - "lil", - "lio", - "lip", - "liq", - "lir", - "lis", - "liu", - "liv", - "liw", - "lix", - "liy", - "liz", - "lja", - "lje", - "lji", - "ljl", - "ljp", - "ljw", - "ljx", - "lka", - "lkb", - "lkc", - "lkd", - "lke", - "lkh", - "lki", - "lkj", - "lkl", - "lkm", - "lkn", - "lko", - "lkr", - "lks", - "lkt", - "lku", - "lky", - "lla", - "llb", - "llc", - "lld", - "lle", - "llf", - "llg", - "llh", - "lli", - "llj", - "llk", - "lll", - "llm", - "lln", - "llo", - "llp", - "llq", - "lls", - "llu", - "llx", - "lma", - "lmb", - "lmc", - "lmd", - "lme", - "lmf", - "lmg", - "lmh", - "lmi", - "lmj", - "lmk", - "lml", - "lmm", - "lmn", - "lmo", - "lmp", - "lmq", - "lmr", - "lmu", - "lmv", - "lmw", - "lmx", - "lmy", - "lmz", - "ln", - "lna", - "lnb", - "lnd", - "lng", - "lnh", - "lni", - "lnj", - "lnl", - "lnm", - "lnn", - "lno", - "lns", - "lnu", - "lnw", - "lnz", - "lo", - "loa", - "lob", - "loc", - "loe", - "lof", - "log", - "loh", - "loi", - "loj", - "lok", - "lol", - "lom", - "lon", - "loo", - "lop", - "loq", - "lor", - "los", - "lot", - "lou", - "lov", - "low", - "lox", - "loy", - "loz", - "lpa", - "lpe", - "lpn", - "lpo", - "lpx", - "lqr", - "lra", - "lrc", - "lre", - "lrg", - "lri", - "lrk", - "lrl", - "lrm", - "lrn", - "lro", - "lrr", - "lrt", - "lrv", - "lrz", - "lsa", - "lsb", - "lsc", - "lsd", - "lse", - "lsg", - "lsh", - "lsi", - "lsl", - "lsm", - "lsn", - "lso", - "lsp", - "lsr", - "lss", - "lst", - "lsv", - "lsw", - "lsy", - "lt", - "ltc", - "ltg", - "lth", - "lti", - "ltn", - "lto", - "lts", - "ltu", - "lu", - "lua", - "luc", - "lud", - "lue", - "luf", - "luh", - "lui", - "luj", - "luk", - "lul", - "lum", - "lun", - "luo", - "lup", - "luq", - "lur", - "lus", - "lut", - "luu", - "luv", - "luw", - "luy", - "luz", - "lv", - "lva", - "lvi", - "lvk", - "lvl", - "lvs", - "lvu", - "lwa", - "lwe", - "lwg", - "lwh", - "lwl", - "lwm", - "lwo", - "lws", - "lwt", - "lwu", - "lww", - "lxm", - "lya", - "lyg", - "lyn", - "lzh", - "lzl", - "lzn", - "lzz", - "maa", - "mab", - "mad", - "mae", - "maf", - "mag", - "mai", - "maj", - "mak", - "mam", - "man", - "map", - "maq", - "mas", - "mat", - "mau", - "mav", - "maw", - "max", - "maz", - "mba", - "mbb", - "mbc", - "mbd", - "mbe", - "mbf", - "mbh", - "mbi", - "mbj", - "mbk", - "mbl", - "mbm", - "mbn", - "mbo", - "mbp", - "mbq", - "mbr", - "mbs", - "mbt", - "mbu", - "mbv", - "mbw", - "mbx", - "mby", - "mbz", - "mca", - "mcb", - "mcc", - "mcd", - "mce", - "mcf", - "mcg", - "mch", - "mci", - "mcj", - "mck", - "mcl", - "mcm", - "mcn", - "mco", - "mcp", - "mcq", - "mcr", - "mcs", - "mct", - "mcu", - "mcv", - "mcw", - "mcx", - "mcy", - "mcz", - "mda", - "mdb", - "mdc", - "mdd", - "mde", - "mdf", - "mdg", - "mdh", - "mdi", - "mdj", - "mdk", - "mdl", - "mdm", - "mdn", - "mdp", - "mdq", - "mdr", - "mds", - "mdt", - "mdu", - "mdv", - "mdw", - "mdx", - "mdy", - "mdz", - "mea", - "meb", - "mec", - "med", - "mee", - "mef", - "meg", - "meh", - "mei", - "mej", - "mek", - "mel", - "mem", - "men", - "meo", - "mep", - "meq", - "mer", - "mes", - "met", - "meu", - "mev", - "mew", - "mey", - "mez", - "mfa", - "mfb", - "mfc", - "mfd", - "mfe", - "mff", - "mfg", - "mfh", - "mfi", - "mfj", - "mfk", - "mfl", - "mfm", - "mfn", - "mfo", - "mfp", - "mfq", - "mfr", - "mfs", - "mft", - "mfu", - "mfv", - "mfw", - "mfx", - "mfy", - "mfz", - "mg", - "mga", - "mgb", - "mgc", - "mgd", - "mge", - "mgf", - "mgg", - "mgh", - "mgi", - "mgj", - "mgk", - "mgl", - "mgm", - "mgn", - "mgo", - "mgp", - "mgq", - "mgr", - "mgs", - "mgt", - "mgu", - "mgv", - "mgw", - "mgx", - "mgy", - "mgz", - "mh", - "mha", - "mhb", - "mhc", - "mhd", - "mhe", - "mhf", - "mhg", - "mhh", - "mhi", - "mhj", - "mhk", - "mhl", - "mhm", - "mhn", - "mho", - "mhp", - "mhq", - "mhr", - "mhs", - "mht", - "mhu", - "mhw", - "mhx", - "mhy", - "mhz", - "mi", - "mia", - "mib", - "mic", - "mid", - "mie", - "mif", - "mig", - "mih", - "mii", - "mij", - "mik", - "mil", - "mim", - "min", - "mio", - "mip", - "miq", - "mir", - "mis", - "mit", - "miu", - "miw", - "mix", - "miy", - "miz", - "mja", - "mjb", - "mjc", - "mjd", - "mje", - "mjg", - "mjh", - "mji", - "mjj", - "mjk", - "mjl", - "mjm", - "mjn", - "mjo", - "mjp", - "mjq", - "mjr", - "mjs", - "mjt", - "mju", - "mjv", - "mjw", - "mjx", - "mjy", - "mjz", - "mk", - "mka", - "mkb", - "mkc", - "mke", - "mkf", - "mkg", - "mkh", - "mki", - "mkj", - "mkk", - "mkl", - "mkm", - "mkn", - "mko", - "mkp", - "mkq", - "mkr", - "mks", - "mkt", - "mku", - "mkv", - "mkw", - "mkx", - "mky", - "mkz", - "ml", - "mla", - "mlb", - "mlc", - "mld", - "mle", - "mlf", - "mlh", - "mli", - "mlj", - "mlk", - "mll", - "mlm", - "mln", - "mlo", - "mlp", - "mlq", - "mlr", - "mls", - "mlu", - "mlv", - "mlw", - "mlx", - "mlz", - "mma", - "mmb", - "mmc", - "mmd", - "mme", - "mmf", - "mmg", - "mmh", - "mmi", - "mmj", - "mmk", - "mml", - "mmm", - "mmn", - "mmo", - "mmp", - "mmq", - "mmr", - "mmt", - "mmu", - "mmv", - "mmw", - "mmx", - "mmy", - "mmz", - "mn", - "mna", - "mnb", - "mnc", - "mnd", - "mne", - "mnf", - "mng", - "mnh", - "mni", - "mnj", - "mnk", - "mnl", - "mnm", - "mnn", - "mno", - "mnp", - "mnq", - "mnr", - "mns", - "mnt", - "mnu", - "mnv", - "mnw", - "mnx", - "mny", - "mnz", - "mo", - "moa", - "moc", - "mod", - "moe", - "mof", - "mog", - "moh", - "moi", - "moj", - "mok", - "mom", - "moo", - "mop", - "moq", - "mor", - "mos", - "mot", - "mou", - "mov", - "mow", - "mox", - "moy", - "moz", - "mpa", - "mpb", - "mpc", - "mpd", - "mpe", - "mpg", - "mph", - "mpi", - "mpj", - "mpk", - "mpl", - "mpm", - "mpn", - "mpo", - "mpp", - "mpq", - "mpr", - "mps", - "mpt", - "mpu", - "mpv", - "mpw", - "mpx", - "mpy", - "mpz", - "mqa", - "mqb", - "mqc", - "mqe", - "mqf", - "mqg", - "mqh", - "mqi", - "mqj", - "mqk", - "mql", - "mqm", - "mqn", - "mqo", - "mqp", - "mqq", - "mqr", - "mqs", - "mqt", - "mqu", - "mqv", - "mqw", - "mqx", - "mqy", - "mqz", - "mr", - "mra", - "mrb", - "mrc", - "mrd", - "mre", - "mrf", - "mrg", - "mrh", - "mrj", - "mrk", - "mrl", - "mrm", - "mrn", - "mro", - "mrp", - "mrq", - "mrr", - "mrs", - "mrt", - "mru", - "mrv", - "mrw", - "mrx", - "mry", - "mrz", - "ms", - "msb", - "msc", - "msd", - "mse", - "msf", - "msg", - "msh", - "msi", - "msj", - "msk", - "msl", - "msm", - "msn", - "mso", - "msp", - "msq", - "msr", - "mss", - "mst", - "msu", - "msv", - "msw", - "msx", - "msy", - "msz", - "mt", - "mta", - "mtb", - "mtc", - "mtd", - "mte", - "mtf", - "mtg", - "mth", - "mti", - "mtj", - "mtk", - "mtl", - "mtm", - "mtn", - "mto", - "mtp", - "mtq", - "mtr", - "mts", - "mtt", - "mtu", - "mtv", - "mtw", - "mtx", - "mty", - "mua", - "mub", - "muc", - "mud", - "mue", - "mug", - "muh", - "mui", - "muj", - "muk", - "mul", - "mum", - "mun", - "muo", - "mup", - "muq", - "mur", - "mus", - "mut", - "muu", - "muv", - "mux", - "muy", - "muz", - "mva", - "mvb", - "mvd", - "mve", - "mvf", - "mvg", - "mvh", - "mvi", - "mvk", - "mvl", - "mvm", - "mvn", - "mvo", - "mvp", - "mvq", - "mvr", - "mvs", - "mvt", - "mvu", - "mvv", - "mvw", - "mvx", - "mvy", - "mvz", - "mwa", - "mwb", - "mwc", - "mwd", - "mwe", - "mwf", - "mwg", - "mwh", - "mwi", - "mwj", - "mwk", - "mwl", - "mwm", - "mwn", - "mwo", - "mwp", - "mwq", - "mwr", - "mws", - "mwt", - "mwu", - "mwv", - "mww", - "mwx", - "mwy", - "mwz", - "mxa", - "mxb", - "mxc", - "mxd", - "mxe", - "mxf", - "mxg", - "mxh", - "mxi", - "mxj", - "mxk", - "mxl", - "mxm", - "mxn", - "mxo", - "mxp", - "mxq", - "mxr", - "mxs", - "mxt", - "mxu", - "mxv", - "mxw", - "mxx", - "mxy", - "mxz", - "my", - "myb", - "myc", - "myd", - "mye", - "myf", - "myg", - "myh", - "myi", - "myj", - "myk", - "myl", - "mym", - "myn", - "myo", - "myp", - "myq", - "myr", - "mys", - "myt", - "myu", - "myv", - "myw", - "myx", - "myy", - "myz", - "mza", - "mzb", - "mzc", - "mzd", - "mze", - "mzg", - "mzh", - "mzi", - "mzj", - "mzk", - "mzl", - "mzm", - "mzn", - "mzo", - "mzp", - "mzq", - "mzr", - "mzs", - "mzt", - "mzu", - "mzv", - "mzw", - "mzx", - "mzy", - "mzz", - "na", - "naa", - "nab", - "nac", - "nad", - "nae", - "naf", - "nag", - "nah", - "nai", - "naj", - "nak", - "nal", - "nam", - "nan", - "nao", - "nap", - "naq", - "nar", - "nas", - "nat", - "naw", - "nax", - "nay", - "naz", - "nb", - "nba", - "nbb", - "nbc", - "nbd", - "nbe", - "nbf", - "nbg", - "nbh", - "nbi", - "nbj", - "nbk", - "nbm", - "nbn", - "nbo", - "nbp", - "nbq", - "nbr", - "nbs", - "nbt", - "nbu", - "nbv", - "nbw", - "nbx", - "nby", - "nca", - "ncb", - "ncc", - "ncd", - "nce", - "ncf", - "ncg", - "nch", - "nci", - "ncj", - "nck", - "ncl", - "ncm", - "ncn", - "nco", - "ncp", - "ncq", - "ncr", - "ncs", - "nct", - "ncu", - "ncx", - "ncz", - "nd", - "nda", - "ndb", - "ndc", - "ndd", - "ndf", - "ndg", - "ndh", - "ndi", - "ndj", - "ndk", - "ndl", - "ndm", - "ndn", - "ndp", - "ndq", - "ndr", - "nds", - "ndt", - "ndu", - "ndv", - "ndw", - "ndx", - "ndy", - "ndz", - "ne", - "nea", - "neb", - "nec", - "ned", - "nee", - "nef", - "neg", - "neh", - "nei", - "nej", - "nek", - "nem", - "nen", - "neo", - "neq", - "ner", - "nes", - "net", - "neu", - "nev", - "new", - "nex", - "ney", - "nez", - "nfa", - "nfd", - "nfl", - "nfr", - "nfu", - "ng", - "nga", - "ngb", - "ngc", - "ngd", - "nge", - "ngf", - "ngg", - "ngh", - "ngi", - "ngj", - "ngk", - "ngl", - "ngm", - "ngn", - "ngo", - "ngp", - "ngq", - "ngr", - "ngs", - "ngt", - "ngu", - "ngv", - "ngw", - "ngx", - "ngy", - "ngz", - "nha", - "nhb", - "nhc", - "nhd", - "nhe", - "nhf", - "nhg", - "nhh", - "nhi", - "nhk", - "nhm", - "nhn", - "nho", - "nhp", - "nhq", - "nhr", - "nht", - "nhu", - "nhv", - "nhw", - "nhx", - "nhy", - "nhz", - "nia", - "nib", - "nic", - "nid", - "nie", - "nif", - "nig", - "nih", - "nii", - "nij", - "nik", - "nil", - "nim", - "nin", - "nio", - "niq", - "nir", - "nis", - "nit", - "niu", - "niv", - "niw", - "nix", - "niy", - "niz", - "nja", - "njb", - "njd", - "njh", - "nji", - "njj", - "njl", - "njm", - "njn", - "njo", - "njr", - "njs", - "njt", - "nju", - "njx", - "njy", - "njz", - "nka", - "nkb", - "nkc", - "nkd", - "nke", - "nkf", - "nkg", - "nkh", - "nki", - "nkj", - "nkk", - "nkm", - "nkn", - "nko", - "nkp", - "nkq", - "nkr", - "nks", - "nkt", - "nku", - "nkv", - "nkw", - "nkx", - "nkz", - "nl", - "nla", - "nlc", - "nle", - "nlg", - "nli", - "nlj", - "nlk", - "nll", - "nlm", - "nln", - "nlo", - "nlq", - "nlr", - "nlu", - "nlv", - "nlw", - "nlx", - "nly", - "nlz", - "nma", - "nmb", - "nmc", - "nmd", - "nme", - "nmf", - "nmg", - "nmh", - "nmi", - "nmj", - "nmk", - "nml", - "nmm", - "nmn", - "nmo", - "nmp", - "nmq", - "nmr", - "nms", - "nmt", - "nmu", - "nmv", - "nmw", - "nmx", - "nmy", - "nmz", - "nn", - "nna", - "nnb", - "nnc", - "nnd", - "nne", - "nnf", - "nng", - "nnh", - "nni", - "nnj", - "nnk", - "nnl", - "nnm", - "nnn", - "nnp", - "nnq", - "nnr", - "nns", - "nnt", - "nnu", - "nnv", - "nnw", - "nnx", - "nny", - "nnz", - "no", - "noa", - "noc", - "nod", - "noe", - "nof", - "nog", - "noh", - "noi", - "noj", - "nok", - "nol", - "nom", - "non", - "noo", - "nop", - "noq", - "nos", - "not", - "nou", - "nov", - "now", - "noy", - "noz", - "npa", - "npb", - "npg", - "nph", - "npi", - "npl", - "npn", - "npo", - "nps", - "npu", - "npx", - "npy", - "nqg", - "nqk", - "nql", - "nqm", - "nqn", - "nqo", - "nqq", - "nqt", - "nqy", - "nr", - "nra", - "nrb", - "nrc", - "nre", - "nrf", - "nrg", - "nri", - "nrk", - "nrl", - "nrm", - "nrn", - "nrp", - "nrr", - "nrt", - "nru", - "nrx", - "nrz", - "nsa", - "nsb", - "nsc", - "nsd", - "nse", - "nsf", - "nsg", - "nsh", - "nsi", - "nsk", - "nsl", - "nsm", - "nsn", - "nso", - "nsp", - "nsq", - "nsr", - "nss", - "nst", - "nsu", - "nsv", - "nsw", - "nsx", - "nsy", - "nsz", - "ntd", - "nte", - "ntg", - "nti", - "ntj", - "ntk", - "ntm", - "nto", - "ntp", - "ntr", - "nts", - "ntu", - "ntw", - "ntx", - "nty", - "ntz", - "nua", - "nub", - "nuc", - "nud", - "nue", - "nuf", - "nug", - "nuh", - "nui", - "nuj", - "nuk", - "nul", - "num", - "nun", - "nuo", - "nup", - "nuq", - "nur", - "nus", - "nut", - "nuu", - "nuv", - "nuw", - "nux", - "nuy", - "nuz", - "nv", - "nvh", - "nvm", - "nvo", - "nwa", - "nwb", - "nwc", - "nwe", - "nwg", - "nwi", - "nwm", - "nwo", - "nwr", - "nww", - "nwx", - "nwy", - "nxa", - "nxd", - "nxe", - "nxg", - "nxi", - "nxk", - "nxl", - "nxm", - "nxn", - "nxo", - "nxq", - "nxr", - "nxu", - "nxx", - "ny", - "nyb", - "nyc", - "nyd", - "nye", - "nyf", - "nyg", - "nyh", - "nyi", - "nyj", - "nyk", - "nyl", - "nym", - "nyn", - "nyo", - "nyp", - "nyq", - "nyr", - "nys", - "nyt", - "nyu", - "nyv", - "nyw", - "nyx", - "nyy", - "nza", - "nzb", - "nzd", - "nzi", - "nzk", - "nzm", - "nzr", - "nzs", - "nzu", - "nzy", - "nzz", - "oaa", - "oac", - "oak", - "oar", - "oav", - "obi", - "obk", - "obl", - "obm", - "obo", - "obr", - "obt", - "obu", - "oc", - "oca", - "och", - "ocm", - "oco", - "ocu", - "oda", - "odk", - "odt", - "odu", - "ofo", - "ofs", - "ofu", - "ogb", - "ogc", - "oge", - "ogg", - "ogo", - "ogu", - "oht", - "ohu", - "oia", - "oie", - "oin", - "oj", - "ojb", - "ojc", - "ojg", - "ojp", - "ojs", - "ojv", - "ojw", - "oka", - "okb", - "okc", - "okd", - "oke", - "okg", - "okh", - "oki", - "okj", - "okk", - "okl", - "okm", - "okn", - "oko", - "okr", - "oks", - "oku", - "okv", - "okx", - "okz", - "ola", - "old", - "ole", - "olk", - "olm", - "olo", - "olr", - "olt", - "olu", - "om", - "oma", - "omb", - "omc", - "ome", - "omg", - "omi", - "omk", - "oml", - "omn", - "omo", - "omp", - "omq", - "omr", - "omt", - "omu", - "omv", - "omw", - "omx", - "omy", - "ona", - "onb", - "one", - "ong", - "oni", - "onj", - "onk", - "onn", - "ono", - "onp", - "onr", - "ons", - "ont", - "onu", - "onw", - "onx", - "ood", - "oog", - "oon", - "oor", - "oos", - "opa", - "opk", - "opm", - "opo", - "opt", - "opy", - "or", - "ora", - "orc", - "ore", - "org", - "orh", - "orn", - "oro", - "orr", - "ors", - "ort", - "oru", - "orv", - "orw", - "orx", - "ory", - "orz", - "os", - "osa", - "osc", - "osi", - "osn", - "oso", - "osp", - "ost", - "osu", - "osx", - "ota", - "otb", - "otd", - "ote", - "oti", - "otk", - "otl", - "otm", - "otn", - "oto", - "otq", - "otr", - "ots", - "ott", - "otu", - "otw", - "otx", - "oty", - "otz", - "oua", - "oub", - "oue", - "oui", - "oum", - "oun", - "ovd", - "owi", - "owl", - "oyb", - "oyd", - "oym", - "oyy", - "ozm", - "pa", - "paa", - "pab", - "pac", - "pad", - "pae", - "paf", - "pag", - "pah", - "pai", - "pak", - "pal", - "pam", - "pao", - "pap", - "paq", - "par", - "pas", - "pat", - "pau", - "pav", - "paw", - "pax", - "pay", - "paz", - "pbb", - "pbc", - "pbe", - "pbf", - "pbg", - "pbh", - "pbi", - "pbl", - "pbm", - "pbn", - "pbo", - "pbp", - "pbr", - "pbs", - "pbt", - "pbu", - "pbv", - "pby", - "pbz", - "pca", - "pcb", - "pcc", - "pcd", - "pce", - "pcf", - "pcg", - "pch", - "pci", - "pcj", - "pck", - "pcl", - "pcm", - "pcn", - "pcp", - "pcr", - "pcw", - "pda", - "pdc", - "pdi", - "pdn", - "pdo", - "pdt", - "pdu", - "pea", - "peb", - "ped", - "pee", - "pef", - "peg", - "peh", - "pei", - "pej", - "pek", - "pel", - "pem", - "peo", - "pep", - "peq", - "pes", - "pev", - "pex", - "pey", - "pez", - "pfa", - "pfe", - "pfl", - "pga", - "pgd", - "pgg", - "pgi", - "pgk", - "pgl", - "pgn", - "pgs", - "pgu", - "pgy", - "pgz", - "pha", - "phd", - "phg", - "phh", - "phi", - "phj", - "phk", - "phl", - "phm", - "phn", - "pho", - "phq", - "phr", - "pht", - "phu", - "phv", - "phw", - "pi", - "pia", - "pib", - "pic", - "pid", - "pie", - "pif", - "pig", - "pih", - "pii", - "pij", - "pil", - "pim", - "pin", - "pio", - "pip", - "pir", - "pis", - "pit", - "piu", - "piv", - "piw", - "pix", - "piy", - "piz", - "pjt", - "pka", - "pkb", - "pkc", - "pkg", - "pkh", - "pkn", - "pko", - "pkp", - "pkr", - "pks", - "pkt", - "pku", - "pl", - "pla", - "plb", - "plc", - "pld", - "ple", - "plf", - "plg", - "plh", - "plj", - "plk", - "pll", - "pln", - "plo", - "plp", - "plq", - "plr", - "pls", - "plt", - "plu", - "plv", - "plw", - "ply", - "plz", - "pma", - "pmb", - "pmc", - "pmd", - "pme", - "pmf", - "pmh", - "pmi", - "pmj", - "pmk", - "pml", - "pmm", - "pmn", - "pmo", - "pmq", - "pmr", - "pms", - "pmt", - "pmu", - "pmw", - "pmx", - "pmy", - "pmz", - "pna", - "pnb", - "pnc", - "pnd", - "pne", - "png", - "pnh", - "pni", - "pnj", - "pnk", - "pnl", - "pnm", - "pnn", - "pno", - "pnp", - "pnq", - "pnr", - "pns", - "pnt", - "pnu", - "pnv", - "pnw", - "pnx", - "pny", - "pnz", - "poc", - "pod", - "poe", - "pof", - "pog", - "poh", - "poi", - "pok", - "pom", - "pon", - "poo", - "pop", - "poq", - "pos", - "pot", - "pov", - "pow", - "pox", - "poy", - "poz", - "ppa", - "ppe", - "ppi", - "ppk", - "ppl", - "ppm", - "ppn", - "ppo", - "ppp", - "ppq", - "ppr", - "pps", - "ppt", - "ppu", - "pqa", - "pqe", - "pqm", - "pqw", - "pra", - "prb", - "prc", - "prd", - "pre", - "prf", - "prg", - "prh", - "pri", - "prk", - "prl", - "prm", - "prn", - "pro", - "prp", - "prq", - "prr", - "prs", - "prt", - "pru", - "prw", - "prx", - "pry", - "prz", - "ps", - "psa", - "psc", - "psd", - "pse", - "psg", - "psh", - "psi", - "psl", - "psm", - "psn", - "pso", - "psp", - "psq", - "psr", - "pss", - "pst", - "psu", - "psw", - "psy", - "pt", - "pta", - "pth", - "pti", - "ptn", - "pto", - "ptp", - "ptq", - "ptr", - "ptt", - "ptu", - "ptv", - "ptw", - "pty", - "pua", - "pub", - "puc", - "pud", - "pue", - "puf", - "pug", - "pui", - "puj", - "puk", - "pum", - "puo", - "pup", - "puq", - "pur", - "put", - "puu", - "puw", - "pux", - "puy", - "puz", - "pwa", - "pwb", - "pwg", - "pwi", - "pwm", - "pwn", - "pwo", - "pwr", - "pww", - "pxm", - "pye", - "pym", - "pyn", - "pys", - "pyu", - "pyx", - "pyy", - "pze", - "pzh", - "pzn", - "qaa..qtz", - "qu", - "qua", - "qub", - "quc", - "qud", - "quf", - "qug", - "quh", - "qui", - "quk", - "qul", - "qum", - "qun", - "qup", - "quq", - "qur", - "qus", - "quv", - "quw", - "qux", - "quy", - "quz", - "qva", - "qvc", - "qve", - "qvh", - "qvi", - "qvj", - "qvl", - "qvm", - "qvn", - "qvo", - "qvp", - "qvs", - "qvw", - "qvy", - "qvz", - "qwa", - "qwc", - "qwe", - "qwh", - "qwm", - "qws", - "qwt", - "qxa", - "qxc", - "qxh", - "qxl", - "qxn", - "qxo", - "qxp", - "qxq", - "qxr", - "qxs", - "qxt", - "qxu", - "qxw", - "qya", - "qyp", - "raa", - "rab", - "rac", - "rad", - "raf", - "rag", - "rah", - "rai", - "raj", - "rak", - "ral", - "ram", - "ran", - "rao", - "rap", - "raq", - "rar", - "ras", - "rat", - "rau", - "rav", - "raw", - "rax", - "ray", - "raz", - "rbb", - "rbk", - "rbl", - "rbp", - "rcf", - "rdb", - "rea", - "reb", - "ree", - "reg", - "rei", - "rej", - "rel", - "rem", - "ren", - "rer", - "res", - "ret", - "rey", - "rga", - "rge", - "rgk", - "rgn", - "rgr", - "rgs", - "rgu", - "rhg", - "rhp", - "ria", - "rib", - "rie", - "rif", - "ril", - "rim", - "rin", - "rir", - "rit", - "riu", - "rjg", - "rji", - "rjs", - "rka", - "rkb", - "rkh", - "rki", - "rkm", - "rkt", - "rkw", - "rm", - "rma", - "rmb", - "rmc", - "rmd", - "rme", - "rmf", - "rmg", - "rmh", - "rmi", - "rmk", - "rml", - "rmm", - "rmn", - "rmo", - "rmp", - "rmq", - "rmr", - "rms", - "rmt", - "rmu", - "rmv", - "rmw", - "rmx", - "rmy", - "rmz", - "rn", - "rna", - "rnb", - "rnd", - "rng", - "rnl", - "rnn", - "rnp", - "rnr", - "rnw", - "ro", - "roa", - "rob", - "roc", - "rod", - "roe", - "rof", - "rog", - "rol", - "rom", - "roo", - "rop", - "ror", - "rou", - "row", - "rpn", - "rpt", - "rri", - "rrm", - "rro", - "rrt", - "rsb", - "rsi", - "rsk", - "rsl", - "rsm", - "rsn", - "rsw", - "rtc", - "rth", - "rtm", - "rts", - "rtw", - "ru", - "rub", - "ruc", - "rue", - "ruf", - "rug", - "ruh", - "rui", - "ruk", - "ruo", - "rup", - "ruq", - "rut", - "ruu", - "ruy", - "ruz", - "rw", - "rwa", - "rwk", - "rwl", - "rwm", - "rwo", - "rwr", - "rxd", - "rxw", - "ryn", - "rys", - "ryu", - "rzh", - "sa", - "saa", - "sab", - "sac", - "sad", - "sae", - "saf", - "sah", - "sai", - "saj", - "sak", - "sal", - "sam", - "sao", - "sap", - "saq", - "sar", - "sas", - "sat", - "sau", - "sav", - "saw", - "sax", - "say", - "saz", - "sba", - "sbb", - "sbc", - "sbd", - "sbe", - "sbf", - "sbg", - "sbh", - "sbi", - "sbj", - "sbk", - "sbl", - "sbm", - "sbn", - "sbo", - "sbp", - "sbq", - "sbr", - "sbs", - "sbt", - "sbu", - "sbv", - "sbw", - "sbx", - "sby", - "sbz", - "sc", - "sca", - "scb", - "sce", - "scf", - "scg", - "sch", - "sci", - "sck", - "scl", - "scn", - "sco", - "scp", - "scq", - "scs", - "sct", - "scu", - "scv", - "scw", - "scx", - "sd", - "sda", - "sdb", - "sdc", - "sde", - "sdf", - "sdg", - "sdh", - "sdj", - "sdk", - "sdl", - "sdm", - "sdn", - "sdo", - "sdp", - "sdq", - "sdr", - "sds", - "sdt", - "sdu", - "sdv", - "sdx", - "sdz", - "se", - "sea", - "seb", - "sec", - "sed", - "see", - "sef", - "seg", - "seh", - "sei", - "sej", - "sek", - "sel", - "sem", - "sen", - "seo", - "sep", - "seq", - "ser", - "ses", - "set", - "seu", - "sev", - "sew", - "sey", - "sez", - "sfb", - "sfe", - "sfm", - "sfs", - "sfw", - "sg", - "sga", - "sgb", - "sgc", - "sgd", - "sge", - "sgg", - "sgh", - "sgi", - "sgj", - "sgk", - "sgl", - "sgm", - "sgn", - "sgo", - "sgp", - "sgr", - "sgs", - "sgt", - "sgu", - "sgw", - "sgx", - "sgy", - "sgz", - "sh", - "sha", - "shb", - "shc", - "shd", - "she", - "shg", - "shh", - "shi", - "shj", - "shk", - "shl", - "shm", - "shn", - "sho", - "shp", - "shq", - "shr", - "shs", - "sht", - "shu", - "shv", - "shw", - "shx", - "shy", - "shz", - "si", - "sia", - "sib", - "sid", - "sie", - "sif", - "sig", - "sih", - "sii", - "sij", - "sik", - "sil", - "sim", - "sio", - "sip", - "siq", - "sir", - "sis", - "sit", - "siu", - "siv", - "siw", - "six", - "siy", - "siz", - "sja", - "sjb", - "sjc", - "sjd", - "sje", - "sjg", - "sjk", - "sjl", - "sjm", - "sjn", - "sjo", - "sjp", - "sjr", - "sjs", - "sjt", - "sju", - "sjw", - "sk", - "ska", - "skb", - "skc", - "skd", - "ske", - "skf", - "skg", - "skh", - "ski", - "skj", - "skk", - "skm", - "skn", - "sko", - "skp", - "skq", - "skr", - "sks", - "skt", - "sku", - "skv", - "skw", - "skx", - "sky", - "skz", - "sl", - "sla", - "slc", - "sld", - "sle", - "slf", - "slg", - "slh", - "sli", - "slj", - "sll", - "slm", - "sln", - "slp", - "slq", - "slr", - "sls", - "slt", - "slu", - "slw", - "slx", - "sly", - "slz", - "sm", - "sma", - "smb", - "smc", - "smd", - "smf", - "smg", - "smh", - "smi", - "smj", - "smk", - "sml", - "smm", - "smn", - "smp", - "smq", - "smr", - "sms", - "smt", - "smu", - "smv", - "smw", - "smx", - "smy", - "smz", - "sn", - "snb", - "snc", - "sne", - "snf", - "sng", - "snh", - "sni", - "snj", - "snk", - "snl", - "snm", - "snn", - "sno", - "snp", - "snq", - "snr", - "sns", - "snu", - "snv", - "snw", - "snx", - "sny", - "snz", - "so", - "soa", - "sob", - "soc", - "sod", - "soe", - "sog", - "soh", - "soi", - "soj", - "sok", - "sol", - "son", - "soo", - "sop", - "soq", - "sor", - "sos", - "sou", - "sov", - "sow", - "sox", - "soy", - "soz", - "spb", - "spc", - "spd", - "spe", - "spg", - "spi", - "spk", - "spl", - "spm", - "spn", - "spo", - "spp", - "spq", - "spr", - "sps", - "spt", - "spu", - "spv", - "spx", - "spy", - "sq", - "sqa", - "sqh", - "sqj", - "sqk", - "sqm", - "sqn", - "sqo", - "sqq", - "sqr", - "sqs", - "sqt", - "squ", - "sqx", - "sr", - "sra", - "srb", - "src", - "sre", - "srf", - "srg", - "srh", - "sri", - "srk", - "srl", - "srm", - "srn", - "sro", - "srq", - "srr", - "srs", - "srt", - "sru", - "srv", - "srw", - "srx", - "sry", - "srz", - "ss", - "ssa", - "ssb", - "ssc", - "ssd", - "sse", - "ssf", - "ssg", - "ssh", - "ssi", - "ssj", - "ssk", - "ssl", - "ssm", - "ssn", - "sso", - "ssp", - "ssq", - "ssr", - "sss", - "sst", - "ssu", - "ssv", - "ssx", - "ssy", - "ssz", - "st", - "sta", - "stb", - "std", - "ste", - "stf", - "stg", - "sth", - "sti", - "stj", - "stk", - "stl", - "stm", - "stn", - "sto", - "stp", - "stq", - "str", - "sts", - "stt", - "stu", - "stv", - "stw", - "sty", - "su", - "sua", - "sub", - "suc", - "sue", - "sug", - "sui", - "suj", - "suk", - "sul", - "sum", - "suo", - "suq", - "sur", - "sus", - "sut", - "suv", - "suw", - "sux", - "suy", - "suz", - "sv", - "sva", - "svb", - "svc", - "sve", - "svk", - "svm", - "svr", - "svs", - "svx", - "sw", - "swb", - "swc", - "swf", - "swg", - "swh", - "swi", - "swj", - "swk", - "swl", - "swm", - "swn", - "swo", - "swp", - "swq", - "swr", - "sws", - "swt", - "swu", - "swv", - "sww", - "swx", - "swy", - "sxb", - "sxc", - "sxe", - "sxg", - "sxk", - "sxl", - "sxm", - "sxn", - "sxo", - "sxr", - "sxs", - "sxu", - "sxw", - "sya", - "syb", - "syc", - "syd", - "syi", - "syk", - "syl", - "sym", - "syn", - "syo", - "syr", - "sys", - "syw", - "syx", - "syy", - "sza", - "szb", - "szc", - "szd", - "sze", - "szg", - "szl", - "szn", - "szp", - "szs", - "szv", - "szw", - "szy", - "ta", - "taa", - "tab", - "tac", - "tad", - "tae", - "taf", - "tag", - "tai", - "taj", - "tak", - "tal", - "tan", - "tao", - "tap", - "taq", - "tar", - "tas", - "tau", - "tav", - "taw", - "tax", - "tay", - "taz", - "tba", - "tbb", - "tbc", - "tbd", - "tbe", - "tbf", - "tbg", - "tbh", - "tbi", - "tbj", - "tbk", - "tbl", - "tbm", - "tbn", - "tbo", - "tbp", - "tbq", - "tbr", - "tbs", - "tbt", - "tbu", - "tbv", - "tbw", - "tbx", - "tby", - "tbz", - "tca", - "tcb", - "tcc", - "tcd", - "tce", - "tcf", - "tcg", - "tch", - "tci", - "tck", - "tcl", - "tcm", - "tcn", - "tco", - "tcp", - "tcq", - "tcs", - "tct", - "tcu", - "tcw", - "tcx", - "tcy", - "tcz", - "tda", - "tdb", - "tdc", - "tdd", - "tde", - "tdf", - "tdg", - "tdh", - "tdi", - "tdj", - "tdk", - "tdl", - "tdm", - "tdn", - "tdo", - "tdq", - "tdr", - "tds", - "tdt", - "tdu", - "tdv", - "tdx", - "tdy", - "te", - "tea", - "teb", - "tec", - "ted", - "tee", - "tef", - "teg", - "teh", - "tei", - "tek", - "tem", - "ten", - "teo", - "tep", - "teq", - "ter", - "tes", - "tet", - "teu", - "tev", - "tew", - "tex", - "tey", - "tez", - "tfi", - "tfn", - "tfo", - "tfr", - "tft", - "tg", - "tga", - "tgb", - "tgc", - "tgd", - "tge", - "tgf", - "tgg", - "tgh", - "tgi", - "tgj", - "tgn", - "tgo", - "tgp", - "tgq", - "tgr", - "tgs", - "tgt", - "tgu", - "tgv", - "tgw", - "tgx", - "tgy", - "tgz", - "th", - "thc", - "thd", - "the", - "thf", - "thh", - "thi", - "thk", - "thl", - "thm", - "thn", - "thp", - "thq", - "thr", - "ths", - "tht", - "thu", - "thv", - "thw", - "thx", - "thy", - "thz", - "ti", - "tia", - "tic", - "tid", - "tie", - "tif", - "tig", - "tih", - "tii", - "tij", - "tik", - "til", - "tim", - "tin", - "tio", - "tip", - "tiq", - "tis", - "tit", - "tiu", - "tiv", - "tiw", - "tix", - "tiy", - "tiz", - "tja", - "tjg", - "tji", - "tjj", - "tjl", - "tjm", - "tjn", - "tjo", - "tjp", - "tjs", - "tju", - "tjw", - "tk", - "tka", - "tkb", - "tkd", - "tke", - "tkf", - "tkg", - "tkk", - "tkl", - "tkm", - "tkn", - "tkp", - "tkq", - "tkr", - "tks", - "tkt", - "tku", - "tkv", - "tkw", - "tkx", - "tkz", - "tl", - "tla", - "tlb", - "tlc", - "tld", - "tlf", - "tlg", - "tlh", - "tli", - "tlj", - "tlk", - "tll", - "tlm", - "tln", - "tlo", - "tlp", - "tlq", - "tlr", - "tls", - "tlt", - "tlu", - "tlv", - "tlw", - "tlx", - "tly", - "tma", - "tmb", - "tmc", - "tmd", - "tme", - "tmf", - "tmg", - "tmh", - "tmi", - "tmj", - "tmk", - "tml", - "tmm", - "tmn", - "tmo", - "tmp", - "tmq", - "tmr", - "tms", - "tmt", - "tmu", - "tmv", - "tmw", - "tmy", - "tmz", - "tn", - "tna", - "tnb", - "tnc", - "tnd", - "tne", - "tnf", - "tng", - "tnh", - "tni", - "tnk", - "tnl", - "tnm", - "tnn", - "tno", - "tnp", - "tnq", - "tnr", - "tns", - "tnt", - "tnu", - "tnv", - "tnw", - "tnx", - "tny", - "tnz", - "to", - "tob", - "toc", - "tod", - "toe", - "tof", - "tog", - "toh", - "toi", - "toj", - "tok", - "tol", - "tom", - "too", - "top", - "toq", - "tor", - "tos", - "tou", - "tov", - "tow", - "tox", - "toy", - "toz", - "tpa", - "tpc", - "tpe", - "tpf", - "tpg", - "tpi", - "tpj", - "tpk", - "tpl", - "tpm", - "tpn", - "tpo", - "tpp", - "tpq", - "tpr", - "tpt", - "tpu", - "tpv", - "tpw", - "tpx", - "tpy", - "tpz", - "tqb", - "tql", - "tqm", - "tqn", - "tqo", - "tqp", - "tqq", - "tqr", - "tqt", - "tqu", - "tqw", - "tr", - "tra", - "trb", - "trc", - "trd", - "tre", - "trf", - "trg", - "trh", - "tri", - "trj", - "trk", - "trl", - "trm", - "trn", - "tro", - "trp", - "trq", - "trr", - "trs", - "trt", - "tru", - "trv", - "trw", - "trx", - "try", - "trz", - "ts", - "tsa", - "tsb", - "tsc", - "tsd", - "tse", - "tsf", - "tsg", - "tsh", - "tsi", - "tsj", - "tsk", - "tsl", - "tsm", - "tsp", - "tsq", - "tsr", - "tss", - "tst", - "tsu", - "tsv", - "tsw", - "tsx", - "tsy", - "tsz", - "tt", - "tta", - "ttb", - "ttc", - "ttd", - "tte", - "ttf", - "ttg", - "tth", - "tti", - "ttj", - "ttk", - "ttl", - "ttm", - "ttn", - "tto", - "ttp", - "ttq", - "ttr", - "tts", - "ttt", - "ttu", - "ttv", - "ttw", - "tty", - "ttz", - "tua", - "tub", - "tuc", - "tud", - "tue", - "tuf", - "tug", - "tuh", - "tui", - "tuj", - "tul", - "tum", - "tun", - "tuo", - "tup", - "tuq", - "tus", - "tut", - "tuu", - "tuv", - "tuw", - "tux", - "tuy", - "tuz", - "tva", - "tvd", - "tve", - "tvi", - "tvk", - "tvl", - "tvm", - "tvn", - "tvo", - "tvs", - "tvt", - "tvu", - "tvw", - "tvx", - "tvy", - "tw", - "twa", - "twb", - "twc", - "twd", - "twe", - "twf", - "twg", - "twh", - "twl", - "twm", - "twn", - "two", - "twp", - "twq", - "twr", - "twt", - "twu", - "tww", - "twx", - "twy", - "txa", - "txb", - "txc", - "txe", - "txg", - "txh", - "txi", - "txj", - "txm", - "txn", - "txo", - "txq", - "txr", - "txs", - "txt", - "txu", - "txx", - "txy", - "ty", - "tya", - "tye", - "tyh", - "tyi", - "tyj", - "tyl", - "tyn", - "typ", - "tyr", - "tys", - "tyt", - "tyu", - "tyv", - "tyx", - "tyy", - "tyz", - "tza", - "tzh", - "tzj", - "tzl", - "tzm", - "tzn", - "tzo", - "tzx", - "uam", - "uan", - "uar", - "uba", - "ubi", - "ubl", - "ubr", - "ubu", - "uby", - "uda", - "ude", - "udg", - "udi", - "udj", - "udl", - "udm", - "udu", - "ues", - "ufi", - "ug", - "uga", - "ugb", - "uge", - "ugh", - "ugn", - "ugo", - "ugy", - "uha", - "uhn", - "uis", - "uiv", - "uji", - "uk", - "uka", - "ukg", - "ukh", - "uki", - "ukk", - "ukl", - "ukp", - "ukq", - "uks", - "uku", - "ukv", - "ukw", - "uky", - "ula", - "ulb", - "ulc", - "ule", - "ulf", - "uli", - "ulk", - "ull", - "ulm", - "uln", - "ulu", - "ulw", - "uly", - "uma", - "umb", - "umc", - "umd", - "umg", - "umi", - "umm", - "umn", - "umo", - "ump", - "umr", - "ums", - "umu", - "una", - "und", - "une", - "ung", - "uni", - "unk", - "unm", - "unn", - "unp", - "unr", - "unu", - "unx", - "unz", - "uok", - "uon", - "upi", - "upv", - "ur", - "ura", - "urb", - "urc", - "ure", - "urf", - "urg", - "urh", - "uri", - "urj", - "urk", - "url", - "urm", - "urn", - "uro", - "urp", - "urr", - "urt", - "uru", - "urv", - "urw", - "urx", - "ury", - "urz", - "usa", - "ush", - "usi", - "usk", - "usp", - "uss", - "usu", - "uta", - "ute", - "uth", - "utp", - "utr", - "utu", - "uum", - "uun", - "uur", - "uuu", - "uve", - "uvh", - "uvl", - "uwa", - "uya", - "uz", - "uzn", - "uzs", - "vaa", - "vae", - "vaf", - "vag", - "vah", - "vai", - "vaj", - "val", - "vam", - "van", - "vao", - "vap", - "var", - "vas", - "vau", - "vav", - "vay", - "vbb", - "vbk", - "ve", - "vec", - "ved", - "vel", - "vem", - "veo", - "vep", - "ver", - "vgr", - "vgt", - "vi", - "vic", - "vid", - "vif", - "vig", - "vil", - "vin", - "vis", - "vit", - "viv", - "vjk", - "vka", - "vki", - "vkj", - "vkk", - "vkl", - "vkm", - "vkn", - "vko", - "vkp", - "vkt", - "vku", - "vkz", - "vlp", - "vls", - "vma", - "vmb", - "vmc", - "vmd", - "vme", - "vmf", - "vmg", - "vmh", - "vmi", - "vmj", - "vmk", - "vml", - "vmm", - "vmp", - "vmq", - "vmr", - "vms", - "vmu", - "vmv", - "vmw", - "vmx", - "vmy", - "vmz", - "vnk", - "vnm", - "vnp", - "vo", - "vor", - "vot", - "vra", - "vro", - "vrs", - "vrt", - "vsi", - "vsl", - "vsn", - "vsv", - "vto", - "vum", - "vun", - "vut", - "vwa", - "wa", - "waa", - "wab", - "wac", - "wad", - "wae", - "waf", - "wag", - "wah", - "wai", - "waj", - "wak", - "wal", - "wam", - "wan", - "wao", - "wap", - "waq", - "war", - "was", - "wat", - "wau", - "wav", - "waw", - "wax", - "way", - "waz", - "wba", - "wbb", - "wbe", - "wbf", - "wbh", - "wbi", - "wbj", - "wbk", - "wbl", - "wbm", - "wbp", - "wbq", - "wbr", - "wbs", - "wbt", - "wbv", - "wbw", - "wca", - "wci", - "wdd", - "wdg", - "wdj", - "wdk", - "wdt", - "wdu", - "wdy", - "wea", - "wec", - "wed", - "weg", - "weh", - "wei", - "wem", - "wen", - "weo", - "wep", - "wer", - "wes", - "wet", - "weu", - "wew", - "wfg", - "wga", - "wgb", - "wgg", - "wgi", - "wgo", - "wgu", - "wgw", - "wgy", - "wha", - "whg", - "whk", - "whu", - "wib", - "wic", - "wie", - "wif", - "wig", - "wih", - "wii", - "wij", - "wik", - "wil", - "wim", - "win", - "wir", - "wit", - "wiu", - "wiv", - "wiw", - "wiy", - "wja", - "wji", - "wka", - "wkb", - "wkd", - "wkl", - "wkr", - "wku", - "wkw", - "wky", - "wla", - "wlc", - "wle", - "wlg", - "wlh", - "wli", - "wlk", - "wll", - "wlm", - "wlo", - "wlr", - "wls", - "wlu", - "wlv", - "wlw", - "wlx", - "wly", - "wma", - "wmb", - "wmc", - "wmd", - "wme", - "wmg", - "wmh", - "wmi", - "wmm", - "wmn", - "wmo", - "wms", - "wmt", - "wmw", - "wmx", - "wnb", - "wnc", - "wnd", - "wne", - "wng", - "wni", - "wnk", - "wnm", - "wnn", - "wno", - "wnp", - "wnu", - "wnw", - "wny", - "wo", - "woa", - "wob", - "woc", - "wod", - "woe", - "wof", - "wog", - "woi", - "wok", - "wom", - "won", - "woo", - "wor", - "wos", - "wow", - "woy", - "wpc", - "wra", - "wrb", - "wrd", - "wrg", - "wrh", - "wri", - "wrk", - "wrl", - "wrm", - "wrn", - "wro", - "wrp", - "wrr", - "wrs", - "wru", - "wrv", - "wrw", - "wrx", - "wry", - "wrz", - "wsa", - "wsg", - "wsi", - "wsk", - "wsr", - "wss", - "wsu", - "wsv", - "wtb", - "wtf", - "wth", - "wti", - "wtk", - "wtm", - "wtw", - "wua", - "wub", - "wud", - "wuh", - "wul", - "wum", - "wun", - "wur", - "wut", - "wuu", - "wuv", - "wux", - "wuy", - "wwa", - "wwb", - "wwo", - "wwr", - "www", - "wxa", - "wxw", - "wya", - "wyb", - "wyi", - "wym", - "wyn", - "wyr", - "wyy", - "xaa", - "xab", - "xac", - "xad", - "xae", - "xag", - "xai", - "xaj", - "xak", - "xal", - "xam", - "xan", - "xao", - "xap", - "xaq", - "xar", - "xas", - "xat", - "xau", - "xav", - "xaw", - "xay", - "xba", - "xbb", - "xbc", - "xbd", - "xbe", - "xbg", - "xbi", - "xbj", - "xbm", - "xbn", - "xbo", - "xbp", - "xbr", - "xbw", - "xbx", - "xby", - "xcb", - "xcc", - "xce", - "xcg", - "xch", - "xcl", - "xcm", - "xcn", - "xco", - "xcr", - "xct", - "xcu", - "xcv", - "xcw", - "xcy", - "xda", - "xdc", - "xdk", - "xdm", - "xdo", - "xdq", - "xdy", - "xeb", - "xed", - "xeg", - "xel", - "xem", - "xep", - "xer", - "xes", - "xet", - "xeu", - "xfa", - "xga", - "xgb", - "xgd", - "xgf", - "xgg", - "xgi", - "xgl", - "xgm", - "xgn", - "xgr", - "xgu", - "xgw", - "xh", - "xha", - "xhc", - "xhd", - "xhe", - "xhm", - "xhr", - "xht", - "xhu", - "xhv", - "xia", - "xib", - "xii", - "xil", - "xin", - "xip", - "xir", - "xis", - "xiv", - "xiy", - "xjb", - "xjt", - "xka", - "xkb", - "xkc", - "xkd", - "xke", - "xkf", - "xkg", - "xkh", - "xki", - "xkj", - "xkk", - "xkl", - "xkn", - "xko", - "xkp", - "xkq", - "xkr", - "xks", - "xkt", - "xku", - "xkv", - "xkw", - "xkx", - "xky", - "xkz", - "xla", - "xlb", - "xlc", - "xld", - "xle", - "xlg", - "xli", - "xln", - "xlo", - "xlp", - "xls", - "xlu", - "xly", - "xma", - "xmb", - "xmc", - "xmd", - "xme", - "xmf", - "xmg", - "xmh", - "xmj", - "xmk", - "xml", - "xmm", - "xmn", - "xmo", - "xmp", - "xmq", - "xmr", - "xms", - "xmt", - "xmu", - "xmv", - "xmw", - "xmx", - "xmy", - "xmz", - "xna", - "xnb", - "xnd", - "xng", - "xnh", - "xni", - "xnj", - "xnk", - "xnm", - "xnn", - "xno", - "xnq", - "xnr", - "xns", - "xnt", - "xnu", - "xny", - "xnz", - "xoc", - "xod", - "xog", - "xoi", - "xok", - "xom", - "xon", - "xoo", - "xop", - "xor", - "xow", - "xpa", - "xpb", - "xpc", - "xpd", - "xpe", - "xpf", - "xpg", - "xph", - "xpi", - "xpj", - "xpk", - "xpl", - "xpm", - "xpn", - "xpo", - "xpp", - "xpq", - "xpr", - "xps", - "xpt", - "xpu", - "xpv", - "xpw", - "xpx", - "xpy", - "xpz", - "xqa", - "xqt", - "xra", - "xrb", - "xrd", - "xre", - "xrg", - "xri", - "xrm", - "xrn", - "xrq", - "xrr", - "xrt", - "xru", - "xrw", - "xsa", - "xsb", - "xsc", - "xsd", - "xse", - "xsh", - "xsi", - "xsj", - "xsl", - "xsm", - "xsn", - "xso", - "xsp", - "xsq", - "xsr", - "xss", - "xsu", - "xsv", - "xsy", - "xta", - "xtb", - "xtc", - "xtd", - "xte", - "xtg", - "xth", - "xti", - "xtj", - "xtl", - "xtm", - "xtn", - "xto", - "xtp", - "xtq", - "xtr", - "xts", - "xtt", - "xtu", - "xtv", - "xtw", - "xty", - "xtz", - "xua", - "xub", - "xud", - "xug", - "xuj", - "xul", - "xum", - "xun", - "xuo", - "xup", - "xur", - "xut", - "xuu", - "xve", - "xvi", - "xvn", - "xvo", - "xvs", - "xwa", - "xwc", - "xwd", - "xwe", - "xwg", - "xwj", - "xwk", - "xwl", - "xwo", - "xwr", - "xwt", - "xww", - "xxb", - "xxk", - "xxm", - "xxr", - "xxt", - "xya", - "xyb", - "xyj", - "xyk", - "xyl", - "xyt", - "xyy", - "xzh", - "xzm", - "xzp", - "yaa", - "yab", - "yac", - "yad", - "yae", - "yaf", - "yag", - "yah", - "yai", - "yaj", - "yak", - "yal", - "yam", - "yan", - "yao", - "yap", - "yaq", - "yar", - "yas", - "yat", - "yau", - "yav", - "yaw", - "yax", - "yay", - "yaz", - "yba", - "ybb", - "ybd", - "ybe", - "ybh", - "ybi", - "ybj", - "ybk", - "ybl", - "ybm", - "ybn", - "ybo", - "ybx", - "yby", - "ych", - "ycl", - "ycn", - "ycp", - "ycr", - "yda", - "ydd", - "yde", - "ydg", - "ydk", - "yds", - "yea", - "yec", - "yee", - "yei", - "yej", - "yel", - "yen", - "yer", - "yes", - "yet", - "yeu", - "yev", - "yey", - "yga", - "ygi", - "ygl", - "ygm", - "ygp", - "ygr", - "ygs", - "ygu", - "ygw", - "yha", - "yhd", - "yhl", - "yhs", - "yi", - "yia", - "yif", - "yig", - "yih", - "yii", - "yij", - "yik", - "yil", - "yim", - "yin", - "yip", - "yiq", - "yir", - "yis", - "yit", - "yiu", - "yiv", - "yix", - "yiy", - "yiz", - "yka", - "ykg", - "ykh", - "yki", - "ykk", - "ykl", - "ykm", - "ykn", - "yko", - "ykr", - "ykt", - "yku", - "yky", - "yla", - "ylb", - "yle", - "ylg", - "yli", - "yll", - "ylm", - "yln", - "ylo", - "ylr", - "ylu", - "yly", - "yma", - "ymb", - "ymc", - "ymd", - "yme", - "ymg", - "ymh", - "ymi", - "ymk", - "yml", - "ymm", - "ymn", - "ymo", - "ymp", - "ymq", - "ymr", - "yms", - "ymt", - "ymx", - "ymz", - "yna", - "ynb", - "ynd", - "yne", - "yng", - "ynh", - "ynk", - "ynl", - "ynn", - "yno", - "ynq", - "yns", - "ynu", - "yo", - "yob", - "yog", - "yoi", - "yok", - "yol", - "yom", - "yon", - "yos", - "yot", - "yox", - "yoy", - "ypa", - "ypb", - "ypg", - "yph", - "ypk", - "ypm", - "ypn", - "ypo", - "ypp", - "ypz", - "yra", - "yrb", - "yre", - "yri", - "yrk", - "yrl", - "yrm", - "yrn", - "yro", - "yrs", - "yrw", - "yry", - "ysc", - "ysd", - "ysg", - "ysl", - "ysm", - "ysn", - "yso", - "ysp", - "ysr", - "yss", - "ysy", - "yta", - "ytl", - "ytp", - "ytw", - "yty", - "yua", - "yub", - "yuc", - "yud", - "yue", - "yuf", - "yug", - "yui", - "yuj", - "yuk", - "yul", - "yum", - "yun", - "yup", - "yuq", - "yur", - "yut", - "yuu", - "yuw", - "yux", - "yuy", - "yuz", - "yva", - "yvt", - "ywa", - "ywg", - "ywl", - "ywn", - "ywq", - "ywr", - "ywt", - "ywu", - "yww", - "yxa", - "yxg", - "yxl", - "yxm", - "yxu", - "yxy", - "yyr", - "yyu", - "yyz", - "yzg", - "yzk", - "za", - "zaa", - "zab", - "zac", - "zad", - "zae", - "zaf", - "zag", - "zah", - "zai", - "zaj", - "zak", - "zal", - "zam", - "zao", - "zap", - "zaq", - "zar", - "zas", - "zat", - "zau", - "zav", - "zaw", - "zax", - "zay", - "zaz", - "zba", - "zbc", - "zbe", - "zbl", - "zbt", - "zbu", - "zbw", - "zca", - "zcd", - "zch", - "zdj", - "zea", - "zeg", - "zeh", - "zem", - "zen", - "zga", - "zgb", - "zgh", - "zgm", - "zgn", - "zgr", - "zh", - "zhb", - "zhd", - "zhi", - "zhn", - "zhw", - "zhx", - "zia", - "zib", - "zik", - "zil", - "zim", - "zin", - "zir", - "ziw", - "ziz", - "zka", - "zkb", - "zkd", - "zkg", - "zkh", - "zkk", - "zkn", - "zko", - "zkp", - "zkr", - "zkt", - "zku", - "zkv", - "zkz", - "zla", - "zle", - "zlj", - "zlm", - "zln", - "zlq", - "zls", - "zlu", - "zlw", - "zma", - "zmb", - "zmc", - "zmd", - "zme", - "zmf", - "zmg", - "zmh", - "zmi", - "zmj", - "zmk", - "zml", - "zmm", - "zmn", - "zmo", - "zmp", - "zmq", - "zmr", - "zms", - "zmt", - "zmu", - "zmv", - "zmw", - "zmx", - "zmy", - "zmz", - "zna", - "znd", - "zne", - "zng", - "znk", - "zns", - "zoc", - "zoh", - "zom", - "zoo", - "zoq", - "zor", - "zos", - "zpa", - "zpb", - "zpc", - "zpd", - "zpe", - "zpf", - "zpg", - "zph", - "zpi", - "zpj", - "zpk", - "zpl", - "zpm", - "zpn", - "zpo", - "zpp", - "zpq", - "zpr", - "zps", - "zpt", - "zpu", - "zpv", - "zpw", - "zpx", - "zpy", - "zpz", - "zqe", - "zra", - "zrg", - "zrn", - "zro", - "zrp", - "zrs", - "zsa", - "zsk", - "zsl", - "zsm", - "zsr", - "zsu", - "zte", - "ztg", - "ztl", - "ztm", - "ztn", - "ztp", - "ztq", - "zts", - "ztt", - "ztu", - "ztx", - "zty", - "zu", - "zua", - "zuh", - "zum", - "zun", - "zuy", - "zwa", - "zxx", - "zyb", - "zyg", - "zyj", - "zyn", - "zyp", - "zza", - "zzj" - ], - "scripts": [ - "Adlm", - "Afak", - "Aghb", - "Ahom", - "Arab", - "Aran", - "Armi", - "Armn", - "Avst", - "Bali", - "Bamu", - "Bass", - "Batk", - "Beng", - "Berf", - "Bhks", - "Blis", - "Bopo", - "Brah", - "Brai", - "Bugi", - "Buhd", - "Cakm", - "Cans", - "Cari", - "Cham", - "Cher", - "Chis", - "Chrs", - "Cirt", - "Copt", - "Cpmn", - "Cprt", - "Cyrl", - "Cyrs", - "Deva", - "Diak", - "Dogr", - "Dsrt", - "Dupl", - "Egyd", - "Egyh", - "Egyp", - "Elba", - "Elym", - "Ethi", - "Gara", - "Geok", - "Geor", - "Glag", - "Gong", - "Gonm", - "Goth", - "Gran", - "Grek", - "Gujr", - "Gukh", - "Guru", - "Hanb", - "Hang", - "Hani", - "Hano", - "Hans", - "Hant", - "Hatr", - "Hebr", - "Hira", - "Hluw", - "Hmng", - "Hmnp", - "Hntl", - "Hrkt", - "Hung", - "Inds", - "Ital", - "Jamo", - "Java", - "Jpan", - "Jurc", - "Kali", - "Kana", - "Kawi", - "Khar", - "Khmr", - "Khoj", - "Kitl", - "Kits", - "Knda", - "Kore", - "Kpel", - "Krai", - "Kthi", - "Lana", - "Laoo", - "Latf", - "Latg", - "Latn", - "Leke", - "Lepc", - "Limb", - "Lina", - "Linb", - "Lisu", - "Loma", - "Lyci", - "Lydi", - "Mahj", - "Maka", - "Mand", - "Mani", - "Marc", - "Maya", - "Medf", - "Mend", - "Merc", - "Mero", - "Mlym", - "Modi", - "Mong", - "Moon", - "Mroo", - "Mtei", - "Mult", - "Mymr", - "Nagm", - "Nand", - "Narb", - "Nbat", - "Newa", - "Nkdb", - "Nkgb", - "Nkoo", - "Nshu", - "Ogam", - "Olck", - "Onao", - "Orkh", - "Orya", - "Osge", - "Osma", - "Ougr", - "Palm", - "Pauc", - "Pcun", - "Pelm", - "Perm", - "Phag", - "Phli", - "Phlp", - "Phlv", - "Phnx", - "Piqd", - "Plrd", - "Prti", - "Psin", - "Qaaa..qabx", - "Ranj", - "Rjng", - "Rohg", - "Roro", - "Runr", - "Samr", - "Sara", - "Sarb", - "Saur", - "Seal", - "Sgnw", - "Shaw", - "Shrd", - "Shui", - "Sidd", - "Sidt", - "Sind", - "Sinh", - "Sogd", - "Sogo", - "Sora", - "Soyo", - "Sund", - "Sunu", - "Sylo", - "Syrc", - "Syre", - "Syrj", - "Syrn", - "Tagb", - "Takr", - "Tale", - "Talu", - "Taml", - "Tang", - "Tavt", - "Tayo", - "Telu", - "Teng", - "Tfng", - "Tglg", - "Thaa", - "Thai", - "Tibt", - "Tirh", - "Tnsa", - "Todr", - "Tols", - "Toto", - "Tutg", - "Ugar", - "Vaii", - "Visp", - "Vith", - "Wara", - "Wcho", - "Wole", - "Xpeo", - "Xsux", - "Yezi", - "Yiii", - "Zanb", - "Zinh", - "Zmth", - "Zsye", - "Zsym", - "Zxxx", - "Zyyy", - "Zzzz" - ], - "regions": [ - "001", - "002", - "003", - "005", - "009", - "011", - "013", - "014", - "015", - "017", - "018", - "019", - "021", - "029", - "030", - "034", - "035", - "039", - "053", - "054", - "057", - "061", - "142", - "143", - "145", - "150", - "151", - "154", - "155", - "202", - "419", - "AA", - "AC", - "AD", - "AE", - "AF", - "AG", - "AI", - "AL", - "AM", - "AN", - "AO", - "AQ", - "AR", - "AS", - "AT", - "AU", - "AW", - "AX", - "AZ", - "BA", - "BB", - "BD", - "BE", - "BF", - "BG", - "BH", - "BI", - "BJ", - "BL", - "BM", - "BN", - "BO", - "BQ", - "BR", - "BS", - "BT", - "BU", - "BV", - "BW", - "BY", - "BZ", - "CA", - "CC", - "CD", - "CF", - "CG", - "CH", - "CI", - "CK", - "CL", - "CM", - "CN", - "CO", - "CP", - "CQ", - "CR", - "CS", - "CU", - "CV", - "CW", - "CX", - "CY", - "CZ", - "DD", - "DE", - "DG", - "DJ", - "DK", - "DM", - "DO", - "DZ", - "EA", - "EC", - "EE", - "EG", - "EH", - "ER", - "ES", - "ET", - "EU", - "EZ", - "FI", - "FJ", - "FK", - "FM", - "FO", - "FR", - "FX", - "GA", - "GB", - "GD", - "GE", - "GF", - "GG", - "GH", - "GI", - "GL", - "GM", - "GN", - "GP", - "GQ", - "GR", - "GS", - "GT", - "GU", - "GW", - "GY", - "HK", - "HM", - "HN", - "HR", - "HT", - "HU", - "IC", - "ID", - "IE", - "IL", - "IM", - "IN", - "IO", - "IQ", - "IR", - "IS", - "IT", - "JE", - "JM", - "JO", - "JP", - "KE", - "KG", - "KH", - "KI", - "KM", - "KN", - "KP", - "KR", - "KW", - "KY", - "KZ", - "LA", - "LB", - "LC", - "LI", - "LK", - "LR", - "LS", - "LT", - "LU", - "LV", - "LY", - "MA", - "MC", - "MD", - "ME", - "MF", - "MG", - "MH", - "MK", - "ML", - "MM", - "MN", - "MO", - "MP", - "MQ", - "MR", - "MS", - "MT", - "MU", - "MV", - "MW", - "MX", - "MY", - "MZ", - "NA", - "NC", - "NE", - "NF", - "NG", - "NI", - "NL", - "NO", - "NP", - "NR", - "NT", - "NU", - "NZ", - "OM", - "PA", - "PE", - "PF", - "PG", - "PH", - "PK", - "PL", - "PM", - "PN", - "PR", - "PS", - "PT", - "PW", - "PY", - "QA", - "QM..QZ", - "RE", - "RO", - "RS", - "RU", - "RW", - "SA", - "SB", - "SC", - "SD", - "SE", - "SG", - "SH", - "SI", - "SJ", - "SK", - "SL", - "SM", - "SN", - "SO", - "SR", - "SS", - "ST", - "SU", - "SV", - "SX", - "SY", - "SZ", - "TA", - "TC", - "TD", - "TF", - "TG", - "TH", - "TJ", - "TK", - "TL", - "TM", - "TN", - "TO", - "TP", - "TR", - "TT", - "TV", - "TW", - "TZ", - "UA", - "UG", - "UM", - "UN", - "US", - "UY", - "UZ", - "VA", - "VC", - "VE", - "VG", - "VI", - "VN", - "VU", - "WF", - "WS", - "XA..XZ", - "YD", - "YE", - "YT", - "YU", - "ZA", - "ZM", - "ZR", - "ZW", - "ZZ" - ], - "variants": [ - "1606nict", - "1694acad", - "1901", - "1959acad", - "1994", - "1996", - "abl1943", - "akuapem", - "alalc97", - "aluku", - "anpezo", - "ao1990", - "aranes", - "arevela", - "arevmda", - "arkaika", - "asante", - "auvern", - "baku1926", - "balanka", - "barla", - "basiceng", - "bauddha", - "bciav", - "bcizbl", - "biscayan", - "biske", - "blasl", - "bohoric", - "boont", - "bornholm", - "cisaup", - "colb1945", - "cornu", - "creiss", - "dajnko", - "ekavsk", - "emodeng", - "fascia", - "fodom", - "fonipa", - "fonkirsh", - "fonnapa", - "fonupa", - "fonxsamp", - "gallo", - "gascon", - "gherd", - "grclass", - "grital", - "grmistr", - "hanoi", - "hepburn", - "heploc", - "hognorsk", - "hsistemo", - "huett", - "ijekavsk", - "itihasa", - "ivanchov", - "jauer", - "jyutping", - "kkcor", - "kleinsch", - "kociewie", - "kscor", - "laukika", - "leidentr", - "lemosin", - "lengadoc", - "lipaw", - "ltg1929", - "ltg2007", - "luna1918", - "mdcegyp", - "mdctrans", - "metelko", - "monoton", - "ndyuka", - "nedis", - "newfound", - "nicard", - "njiva", - "nulik", - "osojs", - "oxendict", - "pahawh2", - "pahawh3", - "pahawh4", - "pamaka", - "peano", - "pehoeji", - "petr1708", - "pinyin", - "polyton", - "provenc", - "puter", - "rigik", - "rozaj", - "rumgr", - "saigon", - "scotland", - "scouse", - "simple", - "solba", - "sotav", - "spanglis", - "surmiran", - "sursilv", - "sutsilv", - "synnejyl", - "tailo", - "tarask", - "tongyong", - "tunumiit", - "uccor", - "ucrcor", - "ulster", - "unifon", - "vaidika", - "valbadia", - "valencia", - "vallader", - "vecdruka", - "viennese", - "vivaraup", - "wadegile", - "xsistemo" - ], - "grandfathered": [ - "art-lojban", - "cel-gaulish", - "en-gb-oed", - "i-ami", - "i-bnn", - "i-default", - "i-enochian", - "i-hak", - "i-klingon", - "i-lux", - "i-mingo", - "i-navajo", - "i-pwn", - "i-tao", - "i-tay", - "i-tsu", - "no-bok", - "no-nyn", - "sgn-be-fr", - "sgn-be-nl", - "sgn-ch-de", - "zh-guoyu", - "zh-hakka", - "zh-min", - "zh-min-nan", - "zh-xiang" - ] -} \ No newline at end of file diff --git a/resources/iana_test.json b/resources/iana_test.json deleted file mode 100644 index e733806..0000000 --- a/resources/iana_test.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "languages": ["en", "fr", "de", "zh"], - "scripts": ["Latn", "Cyrl", "Hans"], - "regions": ["US", "FR", "DE", "CN"], - "variants": ["1901", "1996"], - "grandfathered": ["i-klingon", "i-navajo"] -} \ No newline at end of file diff --git a/src/BCP47Tag.php b/src/BCP47Tag.php index 74b85aa..098624d 100644 --- a/src/BCP47Tag.php +++ b/src/BCP47Tag.php @@ -4,105 +4,98 @@ namespace LHcze\BCP47; -use InvalidArgumentException; use JsonSerializable; +use LHcze\BCP47\Exception\BCP47IanaRegistryException; +use LHcze\BCP47\Exception\BCP47InvalidArgumentException; +use LHcze\BCP47\Exception\BCP47InvalidFallbackLocaleException; +use LHcze\BCP47\Exception\BCP47InvalidLocaleException; +use LHcze\BCP47\Exception\BCP47InvalidMatchingTagException; +use LHcze\BCP47\Exception\BCP47ParserException; use LHcze\BCP47\Normalizer\BCP47Normalizer; use LHcze\BCP47\Parser\BCP47Parser; use LHcze\BCP47\Registry\IanaSubtagRegistry; +use LHcze\BCP47\ValueObject\LanguageTag; use LHcze\BCP47\ValueObject\ParsedTag; use Stringable; final readonly class BCP47Tag implements Stringable, JsonSerializable { /** RFC 5646 representation of a lang-tag */ - private string $locale; - - /** Original input string */ - private string $originalInput; + private LanguageTag $locale; /** IANA Language Subtag Registry */ private IanaSubtagRegistry $registry; + /** BCP47 Normalizer */ + private BCP47Normalizer $normalizer; + + /** BCP47 Parser */ + private BCP47Parser $parser; + /** - * @param string $locale Raw string locale input + * Create a new BCP47Tag instance with a LanguageTag value object + * + * It's intended to carry the BCP47Tag instance around as is, but you can also use LanguageTag directly if you + * want something more lightweight and verbally simple. + * + * NOTE: One specific behavior for fallback locale is, that when the inputLocale is valid, but not to be found + * in the matchTags, the fallback won't be used. + * This is because the purpose of fallback is to only replace inputLocale if it's not valid. + * + * @param string $inputLocale Raw string locale input * @param string|null $fallbackLocale If everything fails, use this locale instead - * @param string[]|null $knownTags A list of known canonical BCP 47 language tags - * @param bool $requireCanonical If true and the locale is language-only (e.g., 'en'), - * it will attempt to match with the first known tag with that language - * and throw an exception if no match is found - * @throws InvalidArgumentException When the locale is invalid and no fallback is provided + * @param string[]|null $useCanonicalMatchTags Locale must match or resolve to one of the canonical tags in the list + * + * @throws BCP47InvalidLocaleException When the locale is invalid and no fallback is provided + * @throws BCP47InvalidFallbackLocaleException When the locale is invalid and so is the provided fallback + * @throws BCP47InvalidMatchingTagException When one of the provided known tags is invalid + * @throws BCP47IanaRegistryException When the IANA registry cannot be loaded + * @throws BCP47InvalidArgumentException */ public function __construct( - string $locale, + private string $inputLocale, ?string $fallbackLocale = null, - ?array $knownTags = null, - bool $requireCanonical = false, + ?array $useCanonicalMatchTags = null, ) { - // Initialize helper classes - $normalizer = new BCP47Normalizer(); - $parser = new BCP47Parser($normalizer); - - // Load the IANA registry - $this->registry = IanaSubtagRegistry::load($parser); + // Initialize hard-working class objects + $this->normalizer = new BCP47Normalizer(); + $this->parser = new BCP47Parser($this->normalizer); - // Store the original input string - $this->originalInput = $locale; + $this->validateOptionalArguments($fallbackLocale, $useCanonicalMatchTags); - // Step 1: Normalize the locale string - $normalized = $normalizer->normalize($locale); - - // Step 2: Process with known tags if available - if ($knownTags !== null) { - $normalizedKnownTags = $parser->parseKnownTags($knownTags); + // Load the IANA registry + $this->registry = IanaSubtagRegistry::load(); - // Try an exact or case-insensitive match first - $matchResult = $parser->findMatchInKnownTags($normalized, $normalizedKnownTags); - if ($matchResult !== null) { - $this->locale = $matchResult; - return; - } + $languageTag = $this->handleValidationNormalizationAndFallback($this->inputLocale, $fallbackLocale); - // Handle language-only case - if (!str_contains($normalized, '-')) { - if ($requireCanonical) { - // When requireCanonical is true, try to find a match or throw exception - $languageMatch = $parser->findLanguageOnlyMatch($normalized, $normalizedKnownTags); - if ($languageMatch !== null) { - $this->locale = $languageMatch; - return; - } - - throw new InvalidArgumentException( - sprintf('No region found for language "%s" in known tags.', $normalized), - ); - } - // When requireCanonical is false, keep the language-only locale as is - } + // Function is enabled if it's not null + if ($useCanonicalMatchTags !== null) { + $languageTag = $this->matchCanonicalTag($languageTag, $useCanonicalMatchTags); } - // Step 3: Handle validation and fallback - $this->locale = $this->handleValidationAndFallback($normalized, $locale, $fallbackLocale, $normalizer); + // Final version of the locale value object + $this->locale = $languageTag; } - public function getOriginalInput(): string + public function getInputLocale(): string { - return $this->originalInput; + return $this->inputLocale; } /** - * Get the normalized locale (default storage format: `xx-XX`). + * Get the normalized locale */ public function getNormalized(): string { - return $this->locale; + return (string) $this->locale; } /** * Get the underscore-separated format (`xx_XX`). */ - public function getUnderscored(): string + public function getICUformat(): string { - return str_replace('-', '_', $this->locale); + return str_replace('-', '_', (string) $this->locale); } /** @@ -111,7 +104,7 @@ public function getUnderscored(): string */ public function getLC(): string { - return strtolower($this->locale); + return strtolower((string) $this->locale); } /** @@ -120,7 +113,7 @@ public function getLC(): string */ public function getUC(): string { - return strtoupper($this->locale); + return strtoupper((string) $this->locale); } /** @@ -144,11 +137,11 @@ public function getUCU(): string /** * Get the parsed tag as a value object * - * @return ParsedTag|null The parsed tag, or null if parsing fails + * @return LanguageTag Language tag value object */ - public function getParsedTag(): ?ParsedTag + public function getLanguageTag(): LanguageTag { - return $this->registry->parseLocale($this->locale); + return $this->locale; } public function jsonSerialize(): string @@ -156,41 +149,168 @@ public function jsonSerialize(): string return $this->getNormalized(); } + /** + * If matchTags and fallback are both provided, validate that fallback is one of the matchTags. + * Before the search, normalize both arguments. + * + * @param string[]|null $useCanonicalMatchTags + * @throws BCP47InvalidArgumentException + */ + public function validateOptionalArguments(?string $fallbackLocale, ?array $useCanonicalMatchTags): void + { + if ($fallbackLocale === null || $useCanonicalMatchTags === null) { + return; + } + + $fallbackLocale = $this->normalizer->normalize($fallbackLocale); + $useCanonicalMatchTags = $this->parser->parseMatchTags($useCanonicalMatchTags); + + if (!in_array($fallbackLocale, $useCanonicalMatchTags, true)) { + throw new BCP47InvalidArgumentException(sprintf( + 'If you want to use matchTags and fallback locale, the fallback must be one of the matchTags. + Fallback: "%s", matchTags: "%s"', + $fallbackLocale, + implode(', ', $useCanonicalMatchTags), + )); + } + } + /** * Handle validation of the locale and fallback if needed + * @throws BCP47InvalidLocaleException + * @throws BCP47InvalidFallbackLocaleException + * @throws BCP47IanaRegistryException */ - private function handleValidationAndFallback( - string $normalized, - string $originalLocale, + private function handleValidationNormalizationAndFallback( + string $inputLocale, ?string $fallbackLocale, - BCP47Normalizer $normalizer, - ): string { - // Validate the locale - if (!$this->isValidLocale($normalized)) { - // If invalid and we have a fallback, use it - if ($fallbackLocale !== null) { - $fallbackNormalized = $normalizer->normalize($fallbackLocale); - if ($this->isValidLocale($fallbackNormalized)) { - return $fallbackNormalized; - } - throw new InvalidArgumentException( - sprintf('Both locale "%s" and fallback locale "%s" are invalid.', $originalLocale, $fallbackLocale), + ): LanguageTag { + $parsedTag = $this->getValidParsedTag($inputLocale); + + if ($parsedTag === null) { + if ($fallbackLocale === null) { + throw new BCP47InvalidLocaleException( + sprintf('Invalid locale format: "%s". No fallback to go to.', $inputLocale), ); } + $parsedTag = $this->getValidParsedTag($fallbackLocale); - throw new InvalidArgumentException(sprintf('Invalid locale format: "%s".', $originalLocale)); + if ($parsedTag === null) { + throw new BCP47InvalidFallbackLocaleException( + sprintf( + 'Both locale "%s" and fallback locale "%s" are invalid.', + $inputLocale, + $fallbackLocale, + ), + ); + } } - // Use the normalized locale if it's valid and no match was found in supported locales - return $normalized; + return LanguageTag::fromValidatedParsedTag($parsedTag); + } + + private function getValidParsedTag(string $languageTag): ?ParsedTag + { + try { + $parsedTag = $this->parser->parseTag($languageTag); + if ($this->registry->isValidParsedTag($parsedTag)) { + return $parsedTag; + } + } catch (BCP47ParserException) { + // Intentionally left empty + } + return null; } /** - * Validate the locale using the IANA Language Subtag Registry + * @throws BCP47ParserException + * @throws BCP47IanaRegistryException */ - private function isValidLocale(string $locale): bool + private function getParsedTag(string $languageTag): ParsedTag { - return $this->registry->isValidLocale($locale); + $parsedTag = $this->parser->parseTag($languageTag); + $this->registry->validateParsedTag($parsedTag); + + return $parsedTag; + } + + /** + * Normalize, validate and evaluate best matching language tag + * + * @param LanguageTag $languageTag Language tag to match + * @param string[] $canonicalMatchTags List of canonical tags to match against + * + * @return LanguageTag The best matching language tag + * @throws BCP47InvalidMatchingTagException + */ + private function matchCanonicalTag(LanguageTag $languageTag, array $canonicalMatchTags): LanguageTag + { + $bestMatch = null; + $bestScore = -1; + + foreach ($canonicalMatchTags as $matchingTag) { + $candidateTag = $this->parseMatchingTag($matchingTag); + + // Basic language must-match β€” skip otherwise + if ($languageTag->getLanguage() !== $candidateTag->getLanguage()) { + continue; + } + + $score = $this->calculateScore($languageTag, $candidateTag); + + if ($score > $bestScore) { + $bestScore = $score; + $bestMatch = $candidateTag; + } + } + + if ($bestMatch === null) { + throw new BCP47InvalidMatchingTagException(sprintf( + 'No matching language tag (%s) found in known tags. Provided: %s', + $languageTag, + implode(', ', $canonicalMatchTags), + )); + } + + return LanguageTag::fromValidatedParsedTag($bestMatch); + } + + private function calculateScore(LanguageTag $a, ParsedTag $b): int + { + // A hundred points for a language match + $score = 100; + + // +10 if a region matches + if ($a->getRegion() !== null && $b->getRegion() !== null && $a->getRegion() === $b->getRegion()) { + $score += 10; + } + + // +1 if a script matches + if ($a->getScript() !== null && $b->getScript() !== null && $a->getScript() === $b->getScript()) { + $score += 1; + } + + return $score; + } + + /** + * @throws BCP47InvalidMatchingTagException + */ + private function parseMatchingTag(string $matchingTag): ParsedTag + { + try { + return $this->getParsedTag($matchingTag); + } catch (BCP47IanaRegistryException $e) { + throw new BCP47InvalidMatchingTagException( + sprintf('Matching language tag "%s" is not a valid IANA tag.', $matchingTag), + previous: $e, + ); + } catch (BCP47ParserException $e) { + throw new BCP47InvalidMatchingTagException( + sprintf('Matching language tag "%s" input is invalid.', $matchingTag), + previous: $e, + ); + } } /** diff --git a/src/Enum/GrandfatheredTag.php b/src/Enum/GrandfatheredTag.php new file mode 100644 index 0000000..5960f5d --- /dev/null +++ b/src/Enum/GrandfatheredTag.php @@ -0,0 +1,40 @@ +value; } // Split into language and region parts diff --git a/src/Parser/BCP47Parser.php b/src/Parser/BCP47Parser.php index bcfe4d0..f1c020f 100644 --- a/src/Parser/BCP47Parser.php +++ b/src/Parser/BCP47Parser.php @@ -4,28 +4,35 @@ namespace LHcze\BCP47\Parser; +use LHcze\BCP47\Enum\GrandfatheredTag; +use LHcze\BCP47\Exception\BCP47ParserException; use LHcze\BCP47\Normalizer\BCP47Normalizer; use LHcze\BCP47\ValueObject\ParsedTag; - +use Throwable; + +/** + * TODO Does not support extensions, transformations and private use tags yet + * @see https://www.rfc-editor.org/rfc/bcp/bcp47.html#tag-syntax + * @see https://www.rfc-editor.org/rfc/rfc4647.html + * @see https://www.rfc-editor.org/rfc/rfc5646.html + * @see https://cldr.unicode.org/development/development-process/design-proposals/bcp47-syntax-mapping + */ final readonly class BCP47Parser { - private BCP47Normalizer $normalizer; - - public function __construct(BCP47Normalizer $normalizer) + public function __construct(private BCP47Normalizer $normalizer) { - $this->normalizer = $normalizer; } /** * Normalize the known tags array - * @param string[] $knownTags + * @param string[] $matchTags * @return string[] */ - public function parseKnownTags(array $knownTags): array + public function parseMatchTags(array $matchTags): array { return array_map( fn(string $loc) => $this->normalizer->normalize($loc), - $knownTags, + $matchTags, ); } @@ -73,53 +80,111 @@ public function findLanguageOnlyMatch(string $language, array $normalizedKnownTa * * @param string $locale The locale string to parse (e.g., 'en-US', 'zh-Hans-CN') * @return ParsedTag The parsed tag + * @throws BCP47ParserException */ public function parseTag(string $locale): ParsedTag { - // Normalize the locale first - $normalized = $this->normalizer->normalize($locale); + try { + // Normalize the locale first + $normalized = $this->normalizer->normalize($locale); + + if ($normalized === '') { + throw new BCP47ParserException('Empty locale.'); + } + + // Get parts from the normalized locale + $parts = $this->getParts($normalized); + + // The first part is always the language + $language = strtolower($parts[0]); + + // Process remaining parts to extract script, region, and variants + $remainingParts = array_slice($parts, 1); + [$script, $region, $variants] = $this->processRemainingParts($remainingParts); + + // TODO: Handle extensions and private use in future versions + + return new ParsedTag($language, $script, $region, $variants); + } catch (Throwable $e) { + throw new BCP47ParserException('Failed to parse locale.', 0, $e); + } + } - // Split the locale into parts - $parts = explode('-', $normalized); + /** + * Get parts from a normalized locale string, handling grandfathered tags + * + * @param string $normalized The normalized locale string + * @return string[] The parts of the locale + */ + private function getParts(string $normalized): array + { + $grandfatheredTag = GrandfatheredTag::tryFrom($normalized); + if ($grandfatheredTag !== null) { + return [$grandfatheredTag->value]; + } - // The first part is always the language - $language = strtolower($parts[0]); + return explode('-', $normalized); + } - // Initialize optional parts + /** + * Process the remaining parts of a locale to extract a script, region, and variants + * + * @param string[] $remainingParts The remaining parts after the language + * @return array{0: ?string, 1: ?string, 2: string[]} Array containing [script, region, variants] + */ + private function processRemainingParts(array $remainingParts): array + { $script = null; $region = null; $variants = []; - // Process remaining parts - $remainingParts = array_slice($parts, 1); $partCount = count($remainingParts); - if ($partCount > 0) { - $index = 0; + if ($partCount === 0) { + return [$script, $region, $variants]; + } - // Check for script (4 letters, first is uppercase, rest lowercase) - if (strlen($remainingParts[$index]) === 4 && ctype_alpha($remainingParts[$index])) { - $script = ucfirst(strtolower($remainingParts[$index])); - $index++; - } + $index = 0; - // Check for a region (2 letters uppercase or 3 digits) - if ($partCount > $index) { - $part = $remainingParts[$index]; - if ((strlen($part) === 2 && ctype_alpha($part)) || (strlen($part) === 3 && ctype_digit($part))) { - $region = strtoupper($part); - $index++; - } - } + // Check for script (4 letters, first is uppercase, rest lowercase) + if ($this->isScript($remainingParts[$index])) { + $script = ucfirst(strtolower($remainingParts[$index])); + $index++; + } - // The remaining parts are variants - if ($partCount > $index) { - $variants = array_slice($remainingParts, $index); - } + // Check for a region (2 letters uppercase or 3 digits) + if ($partCount > $index && $this->isRegion($remainingParts[$index])) { + $region = strtoupper($remainingParts[$index]); + $index++; } - // TODO: Handle extensions and private use in future versions + // The remaining parts are variants + if ($partCount > $index) { + $variants = array_slice($remainingParts, $index); + } - return new ParsedTag($language, $script, $region, $variants); + return [$script, $region, $variants]; + } + + /** + * Check if a part is a script subtag + * + * @param string $part The part to check + * @return bool True if the part is a script subtag + */ + private function isScript(string $part): bool + { + return strlen($part) === 4 && ctype_alpha($part); + } + + /** + * Check if a part is a region subtag + * + * @param string $part The part to check + * @return bool True if the part is a region subtag + */ + private function isRegion(string $part): bool + { + return (strlen($part) === 2 && ctype_alpha($part)) || (strlen($part) === 3 && ctype_digit($part)); } } diff --git a/src/Registry/IanaSubtagRegistry.php b/src/Registry/IanaSubtagRegistry.php index 7b9972f..1a2168c 100644 --- a/src/Registry/IanaSubtagRegistry.php +++ b/src/Registry/IanaSubtagRegistry.php @@ -4,54 +4,27 @@ namespace LHcze\BCP47\Registry; -use LHcze\BCP47\Normalizer\BCP47Normalizer; -use LHcze\BCP47\Parser\BCP47Parser; +use LHcze\BCP47\Exception\BCP47IanaRegistryException; use LHcze\BCP47\ValueObject\ParsedTag; -use RuntimeException; -use Throwable; final class IanaSubtagRegistry { /** - * Array of valid language subtags - * @var string[] - */ - private readonly array $languages; - - /** - * Array of valid script subtags - * @var string[] - */ - private readonly array $scripts; - - /** - * Array of valid region subtags - * @var string[] - */ - private readonly array $regions; - - /** - * Array of valid variant subtags - * @var string[] - */ - private readonly array $variants; - - /** - * Array of grandfathered tags - * @var string[] - */ - private readonly array $grandfathered; - - /** - * Parser for BCP47 tags - */ - private readonly BCP47Parser $parser; - - /** - * Static cache for the registry data - * @var array>|null + * + * Static cache for the registry data. + * Default yet empty structure ensures the safety. + * + * NOTE: keep the order of the keys in the array the same as arguments in the __construct() method! + * + * @var array */ - private static ?array $cache = null; + private static array $cache = [ + 'languages' => [], + 'scripts' => [], + 'regions' => [], + 'variants' => [], + 'grandfathered' => [], + ]; /** * Static instance of the registry @@ -59,81 +32,69 @@ final class IanaSubtagRegistry private static ?self $instance = null; /** + * IANA Language Subtag Registry [RFC5646] + * Registry file: https://www.iana.org/assignments/language-subtag-registry + * + * TODO + * Validates that each of the components exists in the IANA LSR registry, however, some variants predicate that + * the region or language subtag (prefix) is mandatory. + * Same goes for the language suppress-script subtag, preferred, collection,... + * This is not in the current scope of this library, but its planned to be added in the future. + * @see https://www.reecedunn.co.uk/schema/2013/iana + * + * In the future, this registry should support additional IANA properties: + * - 'extlang' An ISO 639-3 language code that belongs to a macrolanguage. + * - 'comments' Notes about the subtag. + * - 'deprecated' The date at which the subtag was deprecated. + * - 'description' + * - 'macrolanguage' The associated parent language. + * - 'preferred-Value' The value that should be used instead of this subtag. + * - 'prefix' The subtag occurring before this subtag. + * - 'supress-script' The default script for the subtag. + * * Private constructor to enforce using the static factory method * - * @param array $languages Array of language subtags - * @param array $scripts Array of script subtags - * @param array $regions Array of region subtags - * @param array $variants Array of variant subtags - * @param array $grandfathered Array of grandfathered tags - * @param BCP47Parser $parser Parser for BCP47 tags + * @param string[] $languages Array of language subtags + * @param string[] $scripts Array of script subtags + * @param string[] $regions Array of region subtags + * @param string[] $variants Array of variant subtags + * @param string[] $grandfathered Array of grandfathered tags */ private function __construct( - array $languages, - array $scripts, - array $regions, - array $variants, - array $grandfathered, - BCP47Parser $parser, + private readonly array $languages, + private readonly array $scripts, + private readonly array $regions, + private readonly array $variants, + private readonly array $grandfathered, ) { - $this->languages = $languages; - $this->scripts = $scripts; - $this->regions = $regions; - $this->variants = $variants; - $this->grandfathered = $grandfathered; - $this->parser = $parser; } /** - * Load the registry data + * Load the registry data and populates the cache. + * + * @return IanaSubtagRegistry The registry instance with the loaded data. * - * @param BCP47Parser|null $parser Optional parser instance (will create one if not provided) - * @return self The registry instance + * @throws BCP47IanaRegistryException */ - public static function load(?BCP47Parser $parser = null): self + public static function load(): self { if (self::$instance !== null) { return self::$instance; } - if (self::$cache === null) { - $registryFile = __DIR__ . '/../Resources/IanaSubtagRegistry.php'; - if (!file_exists($registryFile)) { - throw new RuntimeException("Registry file not found: $registryFile"); - } - - self::$cache = require $registryFile; + $registryFile = __DIR__ . '/../Resources/IanaSubtagRegistryResource.php'; + if (!file_exists($registryFile)) { + throw new BCP47IanaRegistryException("Registry file not found: $registryFile"); } - // Create a parser if one wasn't provided - if ($parser === null) { - $normalizer = new BCP47Normalizer(); - $parser = new BCP47Parser($normalizer); + $registryData = require $registryFile; + if (!is_array($registryData)) { + throw new BCP47IanaRegistryException("Registry resource file must return an array: $registryFile"); } - self::$instance = new self( - self::$cache['languages'] ?? [], - self::$cache['scripts'] ?? [], - self::$cache['regions'] ?? [], - self::$cache['variants'] ?? [], - self::$cache['grandfathered'] ?? [], - $parser, - ); + self::cacheRegistryData($registryData); - return self::$instance; - } - - /** - * For backward compatibility - load from a JSON file - * - * @param string $path Path to the JSON file - * @param BCP47Parser|null $parser Optional parser instance (will create one if not provided) - * @throws RuntimeException If the file cannot be read or parsed - * @deprecated Use load() instead - */ - public static function loadFromFile(string $path, ?BCP47Parser $parser = null): self - { - return self::load($parser); + return self::$instance = new IanaSubtagRegistry(...self::$cache); } /** @@ -144,7 +105,7 @@ public static function loadFromFile(string $path, ?BCP47Parser $parser = null): */ public function isValidLanguage(string $language): bool { - return in_array(strtolower($language), $this->languages, true); + return in_array($language, $this->languages, true); } /** @@ -155,7 +116,7 @@ public function isValidLanguage(string $language): bool */ public function isValidScript(string $script): bool { - return in_array(ucfirst(strtolower($script)), $this->scripts, true); + return in_array($script, $this->scripts, true); } /** @@ -166,7 +127,7 @@ public function isValidScript(string $script): bool */ public function isValidRegion(string $region): bool { - return in_array(strtoupper($region), $this->regions, true); + return in_array($region, $this->regions, true); } /** @@ -177,7 +138,7 @@ public function isValidRegion(string $region): bool */ public function isValidVariant(string $variant): bool { - return in_array(strtolower($variant), $this->variants, true); + return in_array($variant, $this->variants, true); } /** @@ -188,53 +149,42 @@ public function isValidVariant(string $variant): bool */ public function isGrandfathered(string $tag): bool { - return in_array(strtolower($tag), $this->grandfathered, true); + return in_array($tag, $this->grandfathered, true); } /** - * Parse a locale string into a ParsedTag object + * Check if a locale is valid, according to IANA registry * - * @param string $locale The locale string to parse - * @return ParsedTag|null The parsed tag, or null if parsing fails - */ - public function parseLocale(string $locale): ?ParsedTag - { - return $this->parseTagInternal($locale); - } - - /** - * Check if a locale is valid according to IANA registry - * - * @param string $locale Locale to check + * @param ParsedTag $parsedTag Locale to check + * @param bool $requireRegion If true, the locale region subtag is mandatory. Otherwise optional * @return bool True if the locale is valid */ - public function isValidLocale(string $locale): bool - { - // First check if it's a grandfathered tag - if ($this->isGrandfathered($locale)) { + public function isValidParsedTag( + ParsedTag $parsedTag, + bool $requireRegion = false, + bool $requireScript = false, + ): bool { + // First, check if it's a grandfathered tag + if ($this->isGrandfathered((string) $parsedTag)) { return true; } - // Parse the locale into its components - $parsedTag = $this->parseTagInternal($locale); - - // If parsing failed, the locale is invalid - if ($parsedTag === null) { - return false; - } - // Validate each component if (!$this->isValidLanguage($parsedTag->getLanguage())) { return false; } - $script = $parsedTag->getScript(); - if ($script !== null && !$this->isValidScript($script)) { + $region = $parsedTag->getRegion(); + if (($requireRegion === true && ($region === null || !$this->isValidRegion($region))) || + ($region !== null && !$this->isValidRegion($region)) + ) { return false; } - $region = $parsedTag->getRegion(); - if ($region !== null && !$this->isValidRegion($region)) { + $script = $parsedTag->getScript(); + if (($requireScript === true && ($script === null || !$this->isValidscript($script))) || + ($script !== null && !$this->isValidscript($script)) + ) { return false; } @@ -249,18 +199,47 @@ public function isValidLocale(string $locale): bool } /** - * Internal method to parse a locale string into a ParsedTag object - * This is used by both parseLocale() and isValidLocale() to avoid duplicate parsing + * @throws BCP47IanaRegistryException + */ + public function validateParsedTag(ParsedTag $parsedTag): void + { + if (!$this->isValidParsedTag($parsedTag)) { + throw new BCP47IanaRegistryException('Invalid parsed tag'); + } + } + + /** + * Validate and put registry data into the static cache + * + * @param mixed[] $registryData The registry data to cache * - * @param string $locale The locale string to parse - * @return ParsedTag|null The parsed tag, or null if parsing fails + * @SuppressWarnings("PHPMD.UnusedLocalVariable") */ - private function parseTagInternal(string $locale): ?ParsedTag + private static function cacheRegistryData(array $registryData): void { - try { - return $this->parser->parseTag($locale); - } catch (Throwable) { - return null; + // Only allow known keys + /** @var array> $allowedKeys */ + $allowedKeys = array_flip(array_keys(self::$cache)); + + foreach ($registryData as $rawKey => $items) { + $part = strtolower((string) $rawKey); + + if (!isset($allowedKeys[$part]) || !is_array($items)) { + continue; // skip unwanted and invalid keys + } + + // Duplicate values but before, convert them to strings + $deduplicated = array_flip( + array_map( + static fn($v) => (string) $v, + array_filter($items, static fn($v) => is_numeric($v) || is_string($v)), + ), + ); + + // Sort the values by their original index + foreach ($deduplicated as $value => $originalIndex) { + self::$cache[$part][] = (string) $value; + } } } } diff --git a/src/Resources/IanaSubtagRegistry.php b/src/Resources/IanaSubtagRegistryResource.php similarity index 66% rename from src/Resources/IanaSubtagRegistry.php rename to src/Resources/IanaSubtagRegistryResource.php index 8d7e94d..46fe4c0 100644 --- a/src/Resources/IanaSubtagRegistry.php +++ b/src/Resources/IanaSubtagRegistryResource.php @@ -3,7 +3,7 @@ declare(strict_types=1); // This file is auto-generated by bin/fetch_iana_registry.php -// Do not edit manually +// Do not edit manually. Run composer iana:refresh to update the resource file. return array ( 'languages' => @@ -5723,2559 +5723,3078 @@ 5712 => 'pze', 5713 => 'pzh', 5714 => 'pzn', - 5715 => 'qaa..qtz', - 5716 => 'qu', - 5717 => 'qua', - 5718 => 'qub', - 5719 => 'quc', - 5720 => 'qud', - 5721 => 'quf', - 5722 => 'qug', - 5723 => 'quh', - 5724 => 'qui', - 5725 => 'quk', - 5726 => 'qul', - 5727 => 'qum', - 5728 => 'qun', - 5729 => 'qup', - 5730 => 'quq', - 5731 => 'qur', - 5732 => 'qus', - 5733 => 'quv', - 5734 => 'quw', - 5735 => 'qux', - 5736 => 'quy', - 5737 => 'quz', - 5738 => 'qva', - 5739 => 'qvc', - 5740 => 'qve', - 5741 => 'qvh', - 5742 => 'qvi', - 5743 => 'qvj', - 5744 => 'qvl', - 5745 => 'qvm', - 5746 => 'qvn', - 5747 => 'qvo', - 5748 => 'qvp', - 5749 => 'qvs', - 5750 => 'qvw', - 5751 => 'qvy', - 5752 => 'qvz', - 5753 => 'qwa', - 5754 => 'qwc', - 5755 => 'qwe', - 5756 => 'qwh', - 5757 => 'qwm', - 5758 => 'qws', - 5759 => 'qwt', - 5760 => 'qxa', - 5761 => 'qxc', - 5762 => 'qxh', - 5763 => 'qxl', - 5764 => 'qxn', - 5765 => 'qxo', - 5766 => 'qxp', - 5767 => 'qxq', - 5768 => 'qxr', - 5769 => 'qxs', - 5770 => 'qxt', - 5771 => 'qxu', - 5772 => 'qxw', - 5773 => 'qya', - 5774 => 'qyp', - 5775 => 'raa', - 5776 => 'rab', - 5777 => 'rac', - 5778 => 'rad', - 5779 => 'raf', - 5780 => 'rag', - 5781 => 'rah', - 5782 => 'rai', - 5783 => 'raj', - 5784 => 'rak', - 5785 => 'ral', - 5786 => 'ram', - 5787 => 'ran', - 5788 => 'rao', - 5789 => 'rap', - 5790 => 'raq', - 5791 => 'rar', - 5792 => 'ras', - 5793 => 'rat', - 5794 => 'rau', - 5795 => 'rav', - 5796 => 'raw', - 5797 => 'rax', - 5798 => 'ray', - 5799 => 'raz', - 5800 => 'rbb', - 5801 => 'rbk', - 5802 => 'rbl', - 5803 => 'rbp', - 5804 => 'rcf', - 5805 => 'rdb', - 5806 => 'rea', - 5807 => 'reb', - 5808 => 'ree', - 5809 => 'reg', - 5810 => 'rei', - 5811 => 'rej', - 5812 => 'rel', - 5813 => 'rem', - 5814 => 'ren', - 5815 => 'rer', - 5816 => 'res', - 5817 => 'ret', - 5818 => 'rey', - 5819 => 'rga', - 5820 => 'rge', - 5821 => 'rgk', - 5822 => 'rgn', - 5823 => 'rgr', - 5824 => 'rgs', - 5825 => 'rgu', - 5826 => 'rhg', - 5827 => 'rhp', - 5828 => 'ria', - 5829 => 'rib', - 5830 => 'rie', - 5831 => 'rif', - 5832 => 'ril', - 5833 => 'rim', - 5834 => 'rin', - 5835 => 'rir', - 5836 => 'rit', - 5837 => 'riu', - 5838 => 'rjg', - 5839 => 'rji', - 5840 => 'rjs', - 5841 => 'rka', - 5842 => 'rkb', - 5843 => 'rkh', - 5844 => 'rki', - 5845 => 'rkm', - 5846 => 'rkt', - 5847 => 'rkw', - 5848 => 'rm', - 5849 => 'rma', - 5850 => 'rmb', - 5851 => 'rmc', - 5852 => 'rmd', - 5853 => 'rme', - 5854 => 'rmf', - 5855 => 'rmg', - 5856 => 'rmh', - 5857 => 'rmi', - 5858 => 'rmk', - 5859 => 'rml', - 5860 => 'rmm', - 5861 => 'rmn', - 5862 => 'rmo', - 5863 => 'rmp', - 5864 => 'rmq', - 5865 => 'rmr', - 5866 => 'rms', - 5867 => 'rmt', - 5868 => 'rmu', - 5869 => 'rmv', - 5870 => 'rmw', - 5871 => 'rmx', - 5872 => 'rmy', - 5873 => 'rmz', - 5874 => 'rn', - 5875 => 'rna', - 5876 => 'rnb', - 5877 => 'rnd', - 5878 => 'rng', - 5879 => 'rnl', - 5880 => 'rnn', - 5881 => 'rnp', - 5882 => 'rnr', - 5883 => 'rnw', - 5884 => 'ro', - 5885 => 'roa', - 5886 => 'rob', - 5887 => 'roc', - 5888 => 'rod', - 5889 => 'roe', - 5890 => 'rof', - 5891 => 'rog', - 5892 => 'rol', - 5893 => 'rom', - 5894 => 'roo', - 5895 => 'rop', - 5896 => 'ror', - 5897 => 'rou', - 5898 => 'row', - 5899 => 'rpn', - 5900 => 'rpt', - 5901 => 'rri', - 5902 => 'rrm', - 5903 => 'rro', - 5904 => 'rrt', - 5905 => 'rsb', - 5906 => 'rsi', - 5907 => 'rsk', - 5908 => 'rsl', - 5909 => 'rsm', - 5910 => 'rsn', - 5911 => 'rsw', - 5912 => 'rtc', - 5913 => 'rth', - 5914 => 'rtm', - 5915 => 'rts', - 5916 => 'rtw', - 5917 => 'ru', - 5918 => 'rub', - 5919 => 'ruc', - 5920 => 'rue', - 5921 => 'ruf', - 5922 => 'rug', - 5923 => 'ruh', - 5924 => 'rui', - 5925 => 'ruk', - 5926 => 'ruo', - 5927 => 'rup', - 5928 => 'ruq', - 5929 => 'rut', - 5930 => 'ruu', - 5931 => 'ruy', - 5932 => 'ruz', - 5933 => 'rw', - 5934 => 'rwa', - 5935 => 'rwk', - 5936 => 'rwl', - 5937 => 'rwm', - 5938 => 'rwo', - 5939 => 'rwr', - 5940 => 'rxd', - 5941 => 'rxw', - 5942 => 'ryn', - 5943 => 'rys', - 5944 => 'ryu', - 5945 => 'rzh', - 5946 => 'sa', - 5947 => 'saa', - 5948 => 'sab', - 5949 => 'sac', - 5950 => 'sad', - 5951 => 'sae', - 5952 => 'saf', - 5953 => 'sah', - 5954 => 'sai', - 5955 => 'saj', - 5956 => 'sak', - 5957 => 'sal', - 5958 => 'sam', - 5959 => 'sao', - 5960 => 'sap', - 5961 => 'saq', - 5962 => 'sar', - 5963 => 'sas', - 5964 => 'sat', - 5965 => 'sau', - 5966 => 'sav', - 5967 => 'saw', - 5968 => 'sax', - 5969 => 'say', - 5970 => 'saz', - 5971 => 'sba', - 5972 => 'sbb', - 5973 => 'sbc', - 5974 => 'sbd', - 5975 => 'sbe', - 5976 => 'sbf', - 5977 => 'sbg', - 5978 => 'sbh', - 5979 => 'sbi', - 5980 => 'sbj', - 5981 => 'sbk', - 5982 => 'sbl', - 5983 => 'sbm', - 5984 => 'sbn', - 5985 => 'sbo', - 5986 => 'sbp', - 5987 => 'sbq', - 5988 => 'sbr', - 5989 => 'sbs', - 5990 => 'sbt', - 5991 => 'sbu', - 5992 => 'sbv', - 5993 => 'sbw', - 5994 => 'sbx', - 5995 => 'sby', - 5996 => 'sbz', - 5997 => 'sc', - 5998 => 'sca', - 5999 => 'scb', - 6000 => 'sce', - 6001 => 'scf', - 6002 => 'scg', - 6003 => 'sch', - 6004 => 'sci', - 6005 => 'sck', - 6006 => 'scl', - 6007 => 'scn', - 6008 => 'sco', - 6009 => 'scp', - 6010 => 'scq', - 6011 => 'scs', - 6012 => 'sct', - 6013 => 'scu', - 6014 => 'scv', - 6015 => 'scw', - 6016 => 'scx', - 6017 => 'sd', - 6018 => 'sda', - 6019 => 'sdb', - 6020 => 'sdc', - 6021 => 'sde', - 6022 => 'sdf', - 6023 => 'sdg', - 6024 => 'sdh', - 6025 => 'sdj', - 6026 => 'sdk', - 6027 => 'sdl', - 6028 => 'sdm', - 6029 => 'sdn', - 6030 => 'sdo', - 6031 => 'sdp', - 6032 => 'sdq', - 6033 => 'sdr', - 6034 => 'sds', - 6035 => 'sdt', - 6036 => 'sdu', - 6037 => 'sdv', - 6038 => 'sdx', - 6039 => 'sdz', - 6040 => 'se', - 6041 => 'sea', - 6042 => 'seb', - 6043 => 'sec', - 6044 => 'sed', - 6045 => 'see', - 6046 => 'sef', - 6047 => 'seg', - 6048 => 'seh', - 6049 => 'sei', - 6050 => 'sej', - 6051 => 'sek', - 6052 => 'sel', - 6053 => 'sem', - 6054 => 'sen', - 6055 => 'seo', - 6056 => 'sep', - 6057 => 'seq', - 6058 => 'ser', - 6059 => 'ses', - 6060 => 'set', - 6061 => 'seu', - 6062 => 'sev', - 6063 => 'sew', - 6064 => 'sey', - 6065 => 'sez', - 6066 => 'sfb', - 6067 => 'sfe', - 6068 => 'sfm', - 6069 => 'sfs', - 6070 => 'sfw', - 6071 => 'sg', - 6072 => 'sga', - 6073 => 'sgb', - 6074 => 'sgc', - 6075 => 'sgd', - 6076 => 'sge', - 6077 => 'sgg', - 6078 => 'sgh', - 6079 => 'sgi', - 6080 => 'sgj', - 6081 => 'sgk', - 6082 => 'sgl', - 6083 => 'sgm', - 6084 => 'sgn', - 6085 => 'sgo', - 6086 => 'sgp', - 6087 => 'sgr', - 6088 => 'sgs', - 6089 => 'sgt', - 6090 => 'sgu', - 6091 => 'sgw', - 6092 => 'sgx', - 6093 => 'sgy', - 6094 => 'sgz', - 6095 => 'sh', - 6096 => 'sha', - 6097 => 'shb', - 6098 => 'shc', - 6099 => 'shd', - 6100 => 'she', - 6101 => 'shg', - 6102 => 'shh', - 6103 => 'shi', - 6104 => 'shj', - 6105 => 'shk', - 6106 => 'shl', - 6107 => 'shm', - 6108 => 'shn', - 6109 => 'sho', - 6110 => 'shp', - 6111 => 'shq', - 6112 => 'shr', - 6113 => 'shs', - 6114 => 'sht', - 6115 => 'shu', - 6116 => 'shv', - 6117 => 'shw', - 6118 => 'shx', - 6119 => 'shy', - 6120 => 'shz', - 6121 => 'si', - 6122 => 'sia', - 6123 => 'sib', - 6124 => 'sid', - 6125 => 'sie', - 6126 => 'sif', - 6127 => 'sig', - 6128 => 'sih', - 6129 => 'sii', - 6130 => 'sij', - 6131 => 'sik', - 6132 => 'sil', - 6133 => 'sim', - 6134 => 'sio', - 6135 => 'sip', - 6136 => 'siq', - 6137 => 'sir', - 6138 => 'sis', - 6139 => 'sit', - 6140 => 'siu', - 6141 => 'siv', - 6142 => 'siw', - 6143 => 'six', - 6144 => 'siy', - 6145 => 'siz', - 6146 => 'sja', - 6147 => 'sjb', - 6148 => 'sjc', - 6149 => 'sjd', - 6150 => 'sje', - 6151 => 'sjg', - 6152 => 'sjk', - 6153 => 'sjl', - 6154 => 'sjm', - 6155 => 'sjn', - 6156 => 'sjo', - 6157 => 'sjp', - 6158 => 'sjr', - 6159 => 'sjs', - 6160 => 'sjt', - 6161 => 'sju', - 6162 => 'sjw', - 6163 => 'sk', - 6164 => 'ska', - 6165 => 'skb', - 6166 => 'skc', - 6167 => 'skd', - 6168 => 'ske', - 6169 => 'skf', - 6170 => 'skg', - 6171 => 'skh', - 6172 => 'ski', - 6173 => 'skj', - 6174 => 'skk', - 6175 => 'skm', - 6176 => 'skn', - 6177 => 'sko', - 6178 => 'skp', - 6179 => 'skq', - 6180 => 'skr', - 6181 => 'sks', - 6182 => 'skt', - 6183 => 'sku', - 6184 => 'skv', - 6185 => 'skw', - 6186 => 'skx', - 6187 => 'sky', - 6188 => 'skz', - 6189 => 'sl', - 6190 => 'sla', - 6191 => 'slc', - 6192 => 'sld', - 6193 => 'sle', - 6194 => 'slf', - 6195 => 'slg', - 6196 => 'slh', - 6197 => 'sli', - 6198 => 'slj', - 6199 => 'sll', - 6200 => 'slm', - 6201 => 'sln', - 6202 => 'slp', - 6203 => 'slq', - 6204 => 'slr', - 6205 => 'sls', - 6206 => 'slt', - 6207 => 'slu', - 6208 => 'slw', - 6209 => 'slx', - 6210 => 'sly', - 6211 => 'slz', - 6212 => 'sm', - 6213 => 'sma', - 6214 => 'smb', - 6215 => 'smc', - 6216 => 'smd', - 6217 => 'smf', - 6218 => 'smg', - 6219 => 'smh', - 6220 => 'smi', - 6221 => 'smj', - 6222 => 'smk', - 6223 => 'sml', - 6224 => 'smm', - 6225 => 'smn', - 6226 => 'smp', - 6227 => 'smq', - 6228 => 'smr', - 6229 => 'sms', - 6230 => 'smt', - 6231 => 'smu', - 6232 => 'smv', - 6233 => 'smw', - 6234 => 'smx', - 6235 => 'smy', - 6236 => 'smz', - 6237 => 'sn', - 6238 => 'snb', - 6239 => 'snc', - 6240 => 'sne', - 6241 => 'snf', - 6242 => 'sng', - 6243 => 'snh', - 6244 => 'sni', - 6245 => 'snj', - 6246 => 'snk', - 6247 => 'snl', - 6248 => 'snm', - 6249 => 'snn', - 6250 => 'sno', - 6251 => 'snp', - 6252 => 'snq', - 6253 => 'snr', - 6254 => 'sns', - 6255 => 'snu', - 6256 => 'snv', - 6257 => 'snw', - 6258 => 'snx', - 6259 => 'sny', - 6260 => 'snz', - 6261 => 'so', - 6262 => 'soa', - 6263 => 'sob', - 6264 => 'soc', - 6265 => 'sod', - 6266 => 'soe', - 6267 => 'sog', - 6268 => 'soh', - 6269 => 'soi', - 6270 => 'soj', - 6271 => 'sok', - 6272 => 'sol', - 6273 => 'son', - 6274 => 'soo', - 6275 => 'sop', - 6276 => 'soq', - 6277 => 'sor', - 6278 => 'sos', - 6279 => 'sou', - 6280 => 'sov', - 6281 => 'sow', - 6282 => 'sox', - 6283 => 'soy', - 6284 => 'soz', - 6285 => 'spb', - 6286 => 'spc', - 6287 => 'spd', - 6288 => 'spe', - 6289 => 'spg', - 6290 => 'spi', - 6291 => 'spk', - 6292 => 'spl', - 6293 => 'spm', - 6294 => 'spn', - 6295 => 'spo', - 6296 => 'spp', - 6297 => 'spq', - 6298 => 'spr', - 6299 => 'sps', - 6300 => 'spt', - 6301 => 'spu', - 6302 => 'spv', - 6303 => 'spx', - 6304 => 'spy', - 6305 => 'sq', - 6306 => 'sqa', - 6307 => 'sqh', - 6308 => 'sqj', - 6309 => 'sqk', - 6310 => 'sqm', - 6311 => 'sqn', - 6312 => 'sqo', - 6313 => 'sqq', - 6314 => 'sqr', - 6315 => 'sqs', - 6316 => 'sqt', - 6317 => 'squ', - 6318 => 'sqx', - 6319 => 'sr', - 6320 => 'sra', - 6321 => 'srb', - 6322 => 'src', - 6323 => 'sre', - 6324 => 'srf', - 6325 => 'srg', - 6326 => 'srh', - 6327 => 'sri', - 6328 => 'srk', - 6329 => 'srl', - 6330 => 'srm', - 6331 => 'srn', - 6332 => 'sro', - 6333 => 'srq', - 6334 => 'srr', - 6335 => 'srs', - 6336 => 'srt', - 6337 => 'sru', - 6338 => 'srv', - 6339 => 'srw', - 6340 => 'srx', - 6341 => 'sry', - 6342 => 'srz', - 6343 => 'ss', - 6344 => 'ssa', - 6345 => 'ssb', - 6346 => 'ssc', - 6347 => 'ssd', - 6348 => 'sse', - 6349 => 'ssf', - 6350 => 'ssg', - 6351 => 'ssh', - 6352 => 'ssi', - 6353 => 'ssj', - 6354 => 'ssk', - 6355 => 'ssl', - 6356 => 'ssm', - 6357 => 'ssn', - 6358 => 'sso', - 6359 => 'ssp', - 6360 => 'ssq', - 6361 => 'ssr', - 6362 => 'sss', - 6363 => 'sst', - 6364 => 'ssu', - 6365 => 'ssv', - 6366 => 'ssx', - 6367 => 'ssy', - 6368 => 'ssz', - 6369 => 'st', - 6370 => 'sta', - 6371 => 'stb', - 6372 => 'std', - 6373 => 'ste', - 6374 => 'stf', - 6375 => 'stg', - 6376 => 'sth', - 6377 => 'sti', - 6378 => 'stj', - 6379 => 'stk', - 6380 => 'stl', - 6381 => 'stm', - 6382 => 'stn', - 6383 => 'sto', - 6384 => 'stp', - 6385 => 'stq', - 6386 => 'str', - 6387 => 'sts', - 6388 => 'stt', - 6389 => 'stu', - 6390 => 'stv', - 6391 => 'stw', - 6392 => 'sty', - 6393 => 'su', - 6394 => 'sua', - 6395 => 'sub', - 6396 => 'suc', - 6397 => 'sue', - 6398 => 'sug', - 6399 => 'sui', - 6400 => 'suj', - 6401 => 'suk', - 6402 => 'sul', - 6403 => 'sum', - 6404 => 'suo', - 6405 => 'suq', - 6406 => 'sur', - 6407 => 'sus', - 6408 => 'sut', - 6409 => 'suv', - 6410 => 'suw', - 6411 => 'sux', - 6412 => 'suy', - 6413 => 'suz', - 6414 => 'sv', - 6415 => 'sva', - 6416 => 'svb', - 6417 => 'svc', - 6418 => 'sve', - 6419 => 'svk', - 6420 => 'svm', - 6421 => 'svr', - 6422 => 'svs', - 6423 => 'svx', - 6424 => 'sw', - 6425 => 'swb', - 6426 => 'swc', - 6427 => 'swf', - 6428 => 'swg', - 6429 => 'swh', - 6430 => 'swi', - 6431 => 'swj', - 6432 => 'swk', - 6433 => 'swl', - 6434 => 'swm', - 6435 => 'swn', - 6436 => 'swo', - 6437 => 'swp', - 6438 => 'swq', - 6439 => 'swr', - 6440 => 'sws', - 6441 => 'swt', - 6442 => 'swu', - 6443 => 'swv', - 6444 => 'sww', - 6445 => 'swx', - 6446 => 'swy', - 6447 => 'sxb', - 6448 => 'sxc', - 6449 => 'sxe', - 6450 => 'sxg', - 6451 => 'sxk', - 6452 => 'sxl', - 6453 => 'sxm', - 6454 => 'sxn', - 6455 => 'sxo', - 6456 => 'sxr', - 6457 => 'sxs', - 6458 => 'sxu', - 6459 => 'sxw', - 6460 => 'sya', - 6461 => 'syb', - 6462 => 'syc', - 6463 => 'syd', - 6464 => 'syi', - 6465 => 'syk', - 6466 => 'syl', - 6467 => 'sym', - 6468 => 'syn', - 6469 => 'syo', - 6470 => 'syr', - 6471 => 'sys', - 6472 => 'syw', - 6473 => 'syx', - 6474 => 'syy', - 6475 => 'sza', - 6476 => 'szb', - 6477 => 'szc', - 6478 => 'szd', - 6479 => 'sze', - 6480 => 'szg', - 6481 => 'szl', - 6482 => 'szn', - 6483 => 'szp', - 6484 => 'szs', - 6485 => 'szv', - 6486 => 'szw', - 6487 => 'szy', - 6488 => 'ta', - 6489 => 'taa', - 6490 => 'tab', - 6491 => 'tac', - 6492 => 'tad', - 6493 => 'tae', - 6494 => 'taf', - 6495 => 'tag', - 6496 => 'tai', - 6497 => 'taj', - 6498 => 'tak', - 6499 => 'tal', - 6500 => 'tan', - 6501 => 'tao', - 6502 => 'tap', - 6503 => 'taq', - 6504 => 'tar', - 6505 => 'tas', - 6506 => 'tau', - 6507 => 'tav', - 6508 => 'taw', - 6509 => 'tax', - 6510 => 'tay', - 6511 => 'taz', - 6512 => 'tba', - 6513 => 'tbb', - 6514 => 'tbc', - 6515 => 'tbd', - 6516 => 'tbe', - 6517 => 'tbf', - 6518 => 'tbg', - 6519 => 'tbh', - 6520 => 'tbi', - 6521 => 'tbj', - 6522 => 'tbk', - 6523 => 'tbl', - 6524 => 'tbm', - 6525 => 'tbn', - 6526 => 'tbo', - 6527 => 'tbp', - 6528 => 'tbq', - 6529 => 'tbr', - 6530 => 'tbs', - 6531 => 'tbt', - 6532 => 'tbu', - 6533 => 'tbv', - 6534 => 'tbw', - 6535 => 'tbx', - 6536 => 'tby', - 6537 => 'tbz', - 6538 => 'tca', - 6539 => 'tcb', - 6540 => 'tcc', - 6541 => 'tcd', - 6542 => 'tce', - 6543 => 'tcf', - 6544 => 'tcg', - 6545 => 'tch', - 6546 => 'tci', - 6547 => 'tck', - 6548 => 'tcl', - 6549 => 'tcm', - 6550 => 'tcn', - 6551 => 'tco', - 6552 => 'tcp', - 6553 => 'tcq', - 6554 => 'tcs', - 6555 => 'tct', - 6556 => 'tcu', - 6557 => 'tcw', - 6558 => 'tcx', - 6559 => 'tcy', - 6560 => 'tcz', - 6561 => 'tda', - 6562 => 'tdb', - 6563 => 'tdc', - 6564 => 'tdd', - 6565 => 'tde', - 6566 => 'tdf', - 6567 => 'tdg', - 6568 => 'tdh', - 6569 => 'tdi', - 6570 => 'tdj', - 6571 => 'tdk', - 6572 => 'tdl', - 6573 => 'tdm', - 6574 => 'tdn', - 6575 => 'tdo', - 6576 => 'tdq', - 6577 => 'tdr', - 6578 => 'tds', - 6579 => 'tdt', - 6580 => 'tdu', - 6581 => 'tdv', - 6582 => 'tdx', - 6583 => 'tdy', - 6584 => 'te', - 6585 => 'tea', - 6586 => 'teb', - 6587 => 'tec', - 6588 => 'ted', - 6589 => 'tee', - 6590 => 'tef', - 6591 => 'teg', - 6592 => 'teh', - 6593 => 'tei', - 6594 => 'tek', - 6595 => 'tem', - 6596 => 'ten', - 6597 => 'teo', - 6598 => 'tep', - 6599 => 'teq', - 6600 => 'ter', - 6601 => 'tes', - 6602 => 'tet', - 6603 => 'teu', - 6604 => 'tev', - 6605 => 'tew', - 6606 => 'tex', - 6607 => 'tey', - 6608 => 'tez', - 6609 => 'tfi', - 6610 => 'tfn', - 6611 => 'tfo', - 6612 => 'tfr', - 6613 => 'tft', - 6614 => 'tg', - 6615 => 'tga', - 6616 => 'tgb', - 6617 => 'tgc', - 6618 => 'tgd', - 6619 => 'tge', - 6620 => 'tgf', - 6621 => 'tgg', - 6622 => 'tgh', - 6623 => 'tgi', - 6624 => 'tgj', - 6625 => 'tgn', - 6626 => 'tgo', - 6627 => 'tgp', - 6628 => 'tgq', - 6629 => 'tgr', - 6630 => 'tgs', - 6631 => 'tgt', - 6632 => 'tgu', - 6633 => 'tgv', - 6634 => 'tgw', - 6635 => 'tgx', - 6636 => 'tgy', - 6637 => 'tgz', - 6638 => 'th', - 6639 => 'thc', - 6640 => 'thd', - 6641 => 'the', - 6642 => 'thf', - 6643 => 'thh', - 6644 => 'thi', - 6645 => 'thk', - 6646 => 'thl', - 6647 => 'thm', - 6648 => 'thn', - 6649 => 'thp', - 6650 => 'thq', - 6651 => 'thr', - 6652 => 'ths', - 6653 => 'tht', - 6654 => 'thu', - 6655 => 'thv', - 6656 => 'thw', - 6657 => 'thx', - 6658 => 'thy', - 6659 => 'thz', - 6660 => 'ti', - 6661 => 'tia', - 6662 => 'tic', - 6663 => 'tid', - 6664 => 'tie', - 6665 => 'tif', - 6666 => 'tig', - 6667 => 'tih', - 6668 => 'tii', - 6669 => 'tij', - 6670 => 'tik', - 6671 => 'til', - 6672 => 'tim', - 6673 => 'tin', - 6674 => 'tio', - 6675 => 'tip', - 6676 => 'tiq', - 6677 => 'tis', - 6678 => 'tit', - 6679 => 'tiu', - 6680 => 'tiv', - 6681 => 'tiw', - 6682 => 'tix', - 6683 => 'tiy', - 6684 => 'tiz', - 6685 => 'tja', - 6686 => 'tjg', - 6687 => 'tji', - 6688 => 'tjj', - 6689 => 'tjl', - 6690 => 'tjm', - 6691 => 'tjn', - 6692 => 'tjo', - 6693 => 'tjp', - 6694 => 'tjs', - 6695 => 'tju', - 6696 => 'tjw', - 6697 => 'tk', - 6698 => 'tka', - 6699 => 'tkb', - 6700 => 'tkd', - 6701 => 'tke', - 6702 => 'tkf', - 6703 => 'tkg', - 6704 => 'tkk', - 6705 => 'tkl', - 6706 => 'tkm', - 6707 => 'tkn', - 6708 => 'tkp', - 6709 => 'tkq', - 6710 => 'tkr', - 6711 => 'tks', - 6712 => 'tkt', - 6713 => 'tku', - 6714 => 'tkv', - 6715 => 'tkw', - 6716 => 'tkx', - 6717 => 'tkz', - 6718 => 'tl', - 6719 => 'tla', - 6720 => 'tlb', - 6721 => 'tlc', - 6722 => 'tld', - 6723 => 'tlf', - 6724 => 'tlg', - 6725 => 'tlh', - 6726 => 'tli', - 6727 => 'tlj', - 6728 => 'tlk', - 6729 => 'tll', - 6730 => 'tlm', - 6731 => 'tln', - 6732 => 'tlo', - 6733 => 'tlp', - 6734 => 'tlq', - 6735 => 'tlr', - 6736 => 'tls', - 6737 => 'tlt', - 6738 => 'tlu', - 6739 => 'tlv', - 6740 => 'tlw', - 6741 => 'tlx', - 6742 => 'tly', - 6743 => 'tma', - 6744 => 'tmb', - 6745 => 'tmc', - 6746 => 'tmd', - 6747 => 'tme', - 6748 => 'tmf', - 6749 => 'tmg', - 6750 => 'tmh', - 6751 => 'tmi', - 6752 => 'tmj', - 6753 => 'tmk', - 6754 => 'tml', - 6755 => 'tmm', - 6756 => 'tmn', - 6757 => 'tmo', - 6758 => 'tmp', - 6759 => 'tmq', - 6760 => 'tmr', - 6761 => 'tms', - 6762 => 'tmt', - 6763 => 'tmu', - 6764 => 'tmv', - 6765 => 'tmw', - 6766 => 'tmy', - 6767 => 'tmz', - 6768 => 'tn', - 6769 => 'tna', - 6770 => 'tnb', - 6771 => 'tnc', - 6772 => 'tnd', - 6773 => 'tne', - 6774 => 'tnf', - 6775 => 'tng', - 6776 => 'tnh', - 6777 => 'tni', - 6778 => 'tnk', - 6779 => 'tnl', - 6780 => 'tnm', - 6781 => 'tnn', - 6782 => 'tno', - 6783 => 'tnp', - 6784 => 'tnq', - 6785 => 'tnr', - 6786 => 'tns', - 6787 => 'tnt', - 6788 => 'tnu', - 6789 => 'tnv', - 6790 => 'tnw', - 6791 => 'tnx', - 6792 => 'tny', - 6793 => 'tnz', - 6794 => 'to', - 6795 => 'tob', - 6796 => 'toc', - 6797 => 'tod', - 6798 => 'toe', - 6799 => 'tof', - 6800 => 'tog', - 6801 => 'toh', - 6802 => 'toi', - 6803 => 'toj', - 6804 => 'tok', - 6805 => 'tol', - 6806 => 'tom', - 6807 => 'too', - 6808 => 'top', - 6809 => 'toq', - 6810 => 'tor', - 6811 => 'tos', - 6812 => 'tou', - 6813 => 'tov', - 6814 => 'tow', - 6815 => 'tox', - 6816 => 'toy', - 6817 => 'toz', - 6818 => 'tpa', - 6819 => 'tpc', - 6820 => 'tpe', - 6821 => 'tpf', - 6822 => 'tpg', - 6823 => 'tpi', - 6824 => 'tpj', - 6825 => 'tpk', - 6826 => 'tpl', - 6827 => 'tpm', - 6828 => 'tpn', - 6829 => 'tpo', - 6830 => 'tpp', - 6831 => 'tpq', - 6832 => 'tpr', - 6833 => 'tpt', - 6834 => 'tpu', - 6835 => 'tpv', - 6836 => 'tpw', - 6837 => 'tpx', - 6838 => 'tpy', - 6839 => 'tpz', - 6840 => 'tqb', - 6841 => 'tql', - 6842 => 'tqm', - 6843 => 'tqn', - 6844 => 'tqo', - 6845 => 'tqp', - 6846 => 'tqq', - 6847 => 'tqr', - 6848 => 'tqt', - 6849 => 'tqu', - 6850 => 'tqw', - 6851 => 'tr', - 6852 => 'tra', - 6853 => 'trb', - 6854 => 'trc', - 6855 => 'trd', - 6856 => 'tre', - 6857 => 'trf', - 6858 => 'trg', - 6859 => 'trh', - 6860 => 'tri', - 6861 => 'trj', - 6862 => 'trk', - 6863 => 'trl', - 6864 => 'trm', - 6865 => 'trn', - 6866 => 'tro', - 6867 => 'trp', - 6868 => 'trq', - 6869 => 'trr', - 6870 => 'trs', - 6871 => 'trt', - 6872 => 'tru', - 6873 => 'trv', - 6874 => 'trw', - 6875 => 'trx', - 6876 => 'try', - 6877 => 'trz', - 6878 => 'ts', - 6879 => 'tsa', - 6880 => 'tsb', - 6881 => 'tsc', - 6882 => 'tsd', - 6883 => 'tse', - 6884 => 'tsf', - 6885 => 'tsg', - 6886 => 'tsh', - 6887 => 'tsi', - 6888 => 'tsj', - 6889 => 'tsk', - 6890 => 'tsl', - 6891 => 'tsm', - 6892 => 'tsp', - 6893 => 'tsq', - 6894 => 'tsr', - 6895 => 'tss', - 6896 => 'tst', - 6897 => 'tsu', - 6898 => 'tsv', - 6899 => 'tsw', - 6900 => 'tsx', - 6901 => 'tsy', - 6902 => 'tsz', - 6903 => 'tt', - 6904 => 'tta', - 6905 => 'ttb', - 6906 => 'ttc', - 6907 => 'ttd', - 6908 => 'tte', - 6909 => 'ttf', - 6910 => 'ttg', - 6911 => 'tth', - 6912 => 'tti', - 6913 => 'ttj', - 6914 => 'ttk', - 6915 => 'ttl', - 6916 => 'ttm', - 6917 => 'ttn', - 6918 => 'tto', - 6919 => 'ttp', - 6920 => 'ttq', - 6921 => 'ttr', - 6922 => 'tts', - 6923 => 'ttt', - 6924 => 'ttu', - 6925 => 'ttv', - 6926 => 'ttw', - 6927 => 'tty', - 6928 => 'ttz', - 6929 => 'tua', - 6930 => 'tub', - 6931 => 'tuc', - 6932 => 'tud', - 6933 => 'tue', - 6934 => 'tuf', - 6935 => 'tug', - 6936 => 'tuh', - 6937 => 'tui', - 6938 => 'tuj', - 6939 => 'tul', - 6940 => 'tum', - 6941 => 'tun', - 6942 => 'tuo', - 6943 => 'tup', - 6944 => 'tuq', - 6945 => 'tus', - 6946 => 'tut', - 6947 => 'tuu', - 6948 => 'tuv', - 6949 => 'tuw', - 6950 => 'tux', - 6951 => 'tuy', - 6952 => 'tuz', - 6953 => 'tva', - 6954 => 'tvd', - 6955 => 'tve', - 6956 => 'tvi', - 6957 => 'tvk', - 6958 => 'tvl', - 6959 => 'tvm', - 6960 => 'tvn', - 6961 => 'tvo', - 6962 => 'tvs', - 6963 => 'tvt', - 6964 => 'tvu', - 6965 => 'tvw', - 6966 => 'tvx', - 6967 => 'tvy', - 6968 => 'tw', - 6969 => 'twa', - 6970 => 'twb', - 6971 => 'twc', - 6972 => 'twd', - 6973 => 'twe', - 6974 => 'twf', - 6975 => 'twg', - 6976 => 'twh', - 6977 => 'twl', - 6978 => 'twm', - 6979 => 'twn', - 6980 => 'two', - 6981 => 'twp', - 6982 => 'twq', - 6983 => 'twr', - 6984 => 'twt', - 6985 => 'twu', - 6986 => 'tww', - 6987 => 'twx', - 6988 => 'twy', - 6989 => 'txa', - 6990 => 'txb', - 6991 => 'txc', - 6992 => 'txe', - 6993 => 'txg', - 6994 => 'txh', - 6995 => 'txi', - 6996 => 'txj', - 6997 => 'txm', - 6998 => 'txn', - 6999 => 'txo', - 7000 => 'txq', - 7001 => 'txr', - 7002 => 'txs', - 7003 => 'txt', - 7004 => 'txu', - 7005 => 'txx', - 7006 => 'txy', - 7007 => 'ty', - 7008 => 'tya', - 7009 => 'tye', - 7010 => 'tyh', - 7011 => 'tyi', - 7012 => 'tyj', - 7013 => 'tyl', - 7014 => 'tyn', - 7015 => 'typ', - 7016 => 'tyr', - 7017 => 'tys', - 7018 => 'tyt', - 7019 => 'tyu', - 7020 => 'tyv', - 7021 => 'tyx', - 7022 => 'tyy', - 7023 => 'tyz', - 7024 => 'tza', - 7025 => 'tzh', - 7026 => 'tzj', - 7027 => 'tzl', - 7028 => 'tzm', - 7029 => 'tzn', - 7030 => 'tzo', - 7031 => 'tzx', - 7032 => 'uam', - 7033 => 'uan', - 7034 => 'uar', - 7035 => 'uba', - 7036 => 'ubi', - 7037 => 'ubl', - 7038 => 'ubr', - 7039 => 'ubu', - 7040 => 'uby', - 7041 => 'uda', - 7042 => 'ude', - 7043 => 'udg', - 7044 => 'udi', - 7045 => 'udj', - 7046 => 'udl', - 7047 => 'udm', - 7048 => 'udu', - 7049 => 'ues', - 7050 => 'ufi', - 7051 => 'ug', - 7052 => 'uga', - 7053 => 'ugb', - 7054 => 'uge', - 7055 => 'ugh', - 7056 => 'ugn', - 7057 => 'ugo', - 7058 => 'ugy', - 7059 => 'uha', - 7060 => 'uhn', - 7061 => 'uis', - 7062 => 'uiv', - 7063 => 'uji', - 7064 => 'uk', - 7065 => 'uka', - 7066 => 'ukg', - 7067 => 'ukh', - 7068 => 'uki', - 7069 => 'ukk', - 7070 => 'ukl', - 7071 => 'ukp', - 7072 => 'ukq', - 7073 => 'uks', - 7074 => 'uku', - 7075 => 'ukv', - 7076 => 'ukw', - 7077 => 'uky', - 7078 => 'ula', - 7079 => 'ulb', - 7080 => 'ulc', - 7081 => 'ule', - 7082 => 'ulf', - 7083 => 'uli', - 7084 => 'ulk', - 7085 => 'ull', - 7086 => 'ulm', - 7087 => 'uln', - 7088 => 'ulu', - 7089 => 'ulw', - 7090 => 'uly', - 7091 => 'uma', - 7092 => 'umb', - 7093 => 'umc', - 7094 => 'umd', - 7095 => 'umg', - 7096 => 'umi', - 7097 => 'umm', - 7098 => 'umn', - 7099 => 'umo', - 7100 => 'ump', - 7101 => 'umr', - 7102 => 'ums', - 7103 => 'umu', - 7104 => 'una', - 7105 => 'und', - 7106 => 'une', - 7107 => 'ung', - 7108 => 'uni', - 7109 => 'unk', - 7110 => 'unm', - 7111 => 'unn', - 7112 => 'unp', - 7113 => 'unr', - 7114 => 'unu', - 7115 => 'unx', - 7116 => 'unz', - 7117 => 'uok', - 7118 => 'uon', - 7119 => 'upi', - 7120 => 'upv', - 7121 => 'ur', - 7122 => 'ura', - 7123 => 'urb', - 7124 => 'urc', - 7125 => 'ure', - 7126 => 'urf', - 7127 => 'urg', - 7128 => 'urh', - 7129 => 'uri', - 7130 => 'urj', - 7131 => 'urk', - 7132 => 'url', - 7133 => 'urm', - 7134 => 'urn', - 7135 => 'uro', - 7136 => 'urp', - 7137 => 'urr', - 7138 => 'urt', - 7139 => 'uru', - 7140 => 'urv', - 7141 => 'urw', - 7142 => 'urx', - 7143 => 'ury', - 7144 => 'urz', - 7145 => 'usa', - 7146 => 'ush', - 7147 => 'usi', - 7148 => 'usk', - 7149 => 'usp', - 7150 => 'uss', - 7151 => 'usu', - 7152 => 'uta', - 7153 => 'ute', - 7154 => 'uth', - 7155 => 'utp', - 7156 => 'utr', - 7157 => 'utu', - 7158 => 'uum', - 7159 => 'uun', - 7160 => 'uur', - 7161 => 'uuu', - 7162 => 'uve', - 7163 => 'uvh', - 7164 => 'uvl', - 7165 => 'uwa', - 7166 => 'uya', - 7167 => 'uz', - 7168 => 'uzn', - 7169 => 'uzs', - 7170 => 'vaa', - 7171 => 'vae', - 7172 => 'vaf', - 7173 => 'vag', - 7174 => 'vah', - 7175 => 'vai', - 7176 => 'vaj', - 7177 => 'val', - 7178 => 'vam', - 7179 => 'van', - 7180 => 'vao', - 7181 => 'vap', - 7182 => 'var', - 7183 => 'vas', - 7184 => 'vau', - 7185 => 'vav', - 7186 => 'vay', - 7187 => 'vbb', - 7188 => 'vbk', - 7189 => 've', - 7190 => 'vec', - 7191 => 'ved', - 7192 => 'vel', - 7193 => 'vem', - 7194 => 'veo', - 7195 => 'vep', - 7196 => 'ver', - 7197 => 'vgr', - 7198 => 'vgt', - 7199 => 'vi', - 7200 => 'vic', - 7201 => 'vid', - 7202 => 'vif', - 7203 => 'vig', - 7204 => 'vil', - 7205 => 'vin', - 7206 => 'vis', - 7207 => 'vit', - 7208 => 'viv', - 7209 => 'vjk', - 7210 => 'vka', - 7211 => 'vki', - 7212 => 'vkj', - 7213 => 'vkk', - 7214 => 'vkl', - 7215 => 'vkm', - 7216 => 'vkn', - 7217 => 'vko', - 7218 => 'vkp', - 7219 => 'vkt', - 7220 => 'vku', - 7221 => 'vkz', - 7222 => 'vlp', - 7223 => 'vls', - 7224 => 'vma', - 7225 => 'vmb', - 7226 => 'vmc', - 7227 => 'vmd', - 7228 => 'vme', - 7229 => 'vmf', - 7230 => 'vmg', - 7231 => 'vmh', - 7232 => 'vmi', - 7233 => 'vmj', - 7234 => 'vmk', - 7235 => 'vml', - 7236 => 'vmm', - 7237 => 'vmp', - 7238 => 'vmq', - 7239 => 'vmr', - 7240 => 'vms', - 7241 => 'vmu', - 7242 => 'vmv', - 7243 => 'vmw', - 7244 => 'vmx', - 7245 => 'vmy', - 7246 => 'vmz', - 7247 => 'vnk', - 7248 => 'vnm', - 7249 => 'vnp', - 7250 => 'vo', - 7251 => 'vor', - 7252 => 'vot', - 7253 => 'vra', - 7254 => 'vro', - 7255 => 'vrs', - 7256 => 'vrt', - 7257 => 'vsi', - 7258 => 'vsl', - 7259 => 'vsn', - 7260 => 'vsv', - 7261 => 'vto', - 7262 => 'vum', - 7263 => 'vun', - 7264 => 'vut', - 7265 => 'vwa', - 7266 => 'wa', - 7267 => 'waa', - 7268 => 'wab', - 7269 => 'wac', - 7270 => 'wad', - 7271 => 'wae', - 7272 => 'waf', - 7273 => 'wag', - 7274 => 'wah', - 7275 => 'wai', - 7276 => 'waj', - 7277 => 'wak', - 7278 => 'wal', - 7279 => 'wam', - 7280 => 'wan', - 7281 => 'wao', - 7282 => 'wap', - 7283 => 'waq', - 7284 => 'war', - 7285 => 'was', - 7286 => 'wat', - 7287 => 'wau', - 7288 => 'wav', - 7289 => 'waw', - 7290 => 'wax', - 7291 => 'way', - 7292 => 'waz', - 7293 => 'wba', - 7294 => 'wbb', - 7295 => 'wbe', - 7296 => 'wbf', - 7297 => 'wbh', - 7298 => 'wbi', - 7299 => 'wbj', - 7300 => 'wbk', - 7301 => 'wbl', - 7302 => 'wbm', - 7303 => 'wbp', - 7304 => 'wbq', - 7305 => 'wbr', - 7306 => 'wbs', - 7307 => 'wbt', - 7308 => 'wbv', - 7309 => 'wbw', - 7310 => 'wca', - 7311 => 'wci', - 7312 => 'wdd', - 7313 => 'wdg', - 7314 => 'wdj', - 7315 => 'wdk', - 7316 => 'wdt', - 7317 => 'wdu', - 7318 => 'wdy', - 7319 => 'wea', - 7320 => 'wec', - 7321 => 'wed', - 7322 => 'weg', - 7323 => 'weh', - 7324 => 'wei', - 7325 => 'wem', - 7326 => 'wen', - 7327 => 'weo', - 7328 => 'wep', - 7329 => 'wer', - 7330 => 'wes', - 7331 => 'wet', - 7332 => 'weu', - 7333 => 'wew', - 7334 => 'wfg', - 7335 => 'wga', - 7336 => 'wgb', - 7337 => 'wgg', - 7338 => 'wgi', - 7339 => 'wgo', - 7340 => 'wgu', - 7341 => 'wgw', - 7342 => 'wgy', - 7343 => 'wha', - 7344 => 'whg', - 7345 => 'whk', - 7346 => 'whu', - 7347 => 'wib', - 7348 => 'wic', - 7349 => 'wie', - 7350 => 'wif', - 7351 => 'wig', - 7352 => 'wih', - 7353 => 'wii', - 7354 => 'wij', - 7355 => 'wik', - 7356 => 'wil', - 7357 => 'wim', - 7358 => 'win', - 7359 => 'wir', - 7360 => 'wit', - 7361 => 'wiu', - 7362 => 'wiv', - 7363 => 'wiw', - 7364 => 'wiy', - 7365 => 'wja', - 7366 => 'wji', - 7367 => 'wka', - 7368 => 'wkb', - 7369 => 'wkd', - 7370 => 'wkl', - 7371 => 'wkr', - 7372 => 'wku', - 7373 => 'wkw', - 7374 => 'wky', - 7375 => 'wla', - 7376 => 'wlc', - 7377 => 'wle', - 7378 => 'wlg', - 7379 => 'wlh', - 7380 => 'wli', - 7381 => 'wlk', - 7382 => 'wll', - 7383 => 'wlm', - 7384 => 'wlo', - 7385 => 'wlr', - 7386 => 'wls', - 7387 => 'wlu', - 7388 => 'wlv', - 7389 => 'wlw', - 7390 => 'wlx', - 7391 => 'wly', - 7392 => 'wma', - 7393 => 'wmb', - 7394 => 'wmc', - 7395 => 'wmd', - 7396 => 'wme', - 7397 => 'wmg', - 7398 => 'wmh', - 7399 => 'wmi', - 7400 => 'wmm', - 7401 => 'wmn', - 7402 => 'wmo', - 7403 => 'wms', - 7404 => 'wmt', - 7405 => 'wmw', - 7406 => 'wmx', - 7407 => 'wnb', - 7408 => 'wnc', - 7409 => 'wnd', - 7410 => 'wne', - 7411 => 'wng', - 7412 => 'wni', - 7413 => 'wnk', - 7414 => 'wnm', - 7415 => 'wnn', - 7416 => 'wno', - 7417 => 'wnp', - 7418 => 'wnu', - 7419 => 'wnw', - 7420 => 'wny', - 7421 => 'wo', - 7422 => 'woa', - 7423 => 'wob', - 7424 => 'woc', - 7425 => 'wod', - 7426 => 'woe', - 7427 => 'wof', - 7428 => 'wog', - 7429 => 'woi', - 7430 => 'wok', - 7431 => 'wom', - 7432 => 'won', - 7433 => 'woo', - 7434 => 'wor', - 7435 => 'wos', - 7436 => 'wow', - 7437 => 'woy', - 7438 => 'wpc', - 7439 => 'wra', - 7440 => 'wrb', - 7441 => 'wrd', - 7442 => 'wrg', - 7443 => 'wrh', - 7444 => 'wri', - 7445 => 'wrk', - 7446 => 'wrl', - 7447 => 'wrm', - 7448 => 'wrn', - 7449 => 'wro', - 7450 => 'wrp', - 7451 => 'wrr', - 7452 => 'wrs', - 7453 => 'wru', - 7454 => 'wrv', - 7455 => 'wrw', - 7456 => 'wrx', - 7457 => 'wry', - 7458 => 'wrz', - 7459 => 'wsa', - 7460 => 'wsg', - 7461 => 'wsi', - 7462 => 'wsk', - 7463 => 'wsr', - 7464 => 'wss', - 7465 => 'wsu', - 7466 => 'wsv', - 7467 => 'wtb', - 7468 => 'wtf', - 7469 => 'wth', - 7470 => 'wti', - 7471 => 'wtk', - 7472 => 'wtm', - 7473 => 'wtw', - 7474 => 'wua', - 7475 => 'wub', - 7476 => 'wud', - 7477 => 'wuh', - 7478 => 'wul', - 7479 => 'wum', - 7480 => 'wun', - 7481 => 'wur', - 7482 => 'wut', - 7483 => 'wuu', - 7484 => 'wuv', - 7485 => 'wux', - 7486 => 'wuy', - 7487 => 'wwa', - 7488 => 'wwb', - 7489 => 'wwo', - 7490 => 'wwr', - 7491 => 'www', - 7492 => 'wxa', - 7493 => 'wxw', - 7494 => 'wya', - 7495 => 'wyb', - 7496 => 'wyi', - 7497 => 'wym', - 7498 => 'wyn', - 7499 => 'wyr', - 7500 => 'wyy', - 7501 => 'xaa', - 7502 => 'xab', - 7503 => 'xac', - 7504 => 'xad', - 7505 => 'xae', - 7506 => 'xag', - 7507 => 'xai', - 7508 => 'xaj', - 7509 => 'xak', - 7510 => 'xal', - 7511 => 'xam', - 7512 => 'xan', - 7513 => 'xao', - 7514 => 'xap', - 7515 => 'xaq', - 7516 => 'xar', - 7517 => 'xas', - 7518 => 'xat', - 7519 => 'xau', - 7520 => 'xav', - 7521 => 'xaw', - 7522 => 'xay', - 7523 => 'xba', - 7524 => 'xbb', - 7525 => 'xbc', - 7526 => 'xbd', - 7527 => 'xbe', - 7528 => 'xbg', - 7529 => 'xbi', - 7530 => 'xbj', - 7531 => 'xbm', - 7532 => 'xbn', - 7533 => 'xbo', - 7534 => 'xbp', - 7535 => 'xbr', - 7536 => 'xbw', - 7537 => 'xbx', - 7538 => 'xby', - 7539 => 'xcb', - 7540 => 'xcc', - 7541 => 'xce', - 7542 => 'xcg', - 7543 => 'xch', - 7544 => 'xcl', - 7545 => 'xcm', - 7546 => 'xcn', - 7547 => 'xco', - 7548 => 'xcr', - 7549 => 'xct', - 7550 => 'xcu', - 7551 => 'xcv', - 7552 => 'xcw', - 7553 => 'xcy', - 7554 => 'xda', - 7555 => 'xdc', - 7556 => 'xdk', - 7557 => 'xdm', - 7558 => 'xdo', - 7559 => 'xdq', - 7560 => 'xdy', - 7561 => 'xeb', - 7562 => 'xed', - 7563 => 'xeg', - 7564 => 'xel', - 7565 => 'xem', - 7566 => 'xep', - 7567 => 'xer', - 7568 => 'xes', - 7569 => 'xet', - 7570 => 'xeu', - 7571 => 'xfa', - 7572 => 'xga', - 7573 => 'xgb', - 7574 => 'xgd', - 7575 => 'xgf', - 7576 => 'xgg', - 7577 => 'xgi', - 7578 => 'xgl', - 7579 => 'xgm', - 7580 => 'xgn', - 7581 => 'xgr', - 7582 => 'xgu', - 7583 => 'xgw', - 7584 => 'xh', - 7585 => 'xha', - 7586 => 'xhc', - 7587 => 'xhd', - 7588 => 'xhe', - 7589 => 'xhm', - 7590 => 'xhr', - 7591 => 'xht', - 7592 => 'xhu', - 7593 => 'xhv', - 7594 => 'xia', - 7595 => 'xib', - 7596 => 'xii', - 7597 => 'xil', - 7598 => 'xin', - 7599 => 'xip', - 7600 => 'xir', - 7601 => 'xis', - 7602 => 'xiv', - 7603 => 'xiy', - 7604 => 'xjb', - 7605 => 'xjt', - 7606 => 'xka', - 7607 => 'xkb', - 7608 => 'xkc', - 7609 => 'xkd', - 7610 => 'xke', - 7611 => 'xkf', - 7612 => 'xkg', - 7613 => 'xkh', - 7614 => 'xki', - 7615 => 'xkj', - 7616 => 'xkk', - 7617 => 'xkl', - 7618 => 'xkn', - 7619 => 'xko', - 7620 => 'xkp', - 7621 => 'xkq', - 7622 => 'xkr', - 7623 => 'xks', - 7624 => 'xkt', - 7625 => 'xku', - 7626 => 'xkv', - 7627 => 'xkw', - 7628 => 'xkx', - 7629 => 'xky', - 7630 => 'xkz', - 7631 => 'xla', - 7632 => 'xlb', - 7633 => 'xlc', - 7634 => 'xld', - 7635 => 'xle', - 7636 => 'xlg', - 7637 => 'xli', - 7638 => 'xln', - 7639 => 'xlo', - 7640 => 'xlp', - 7641 => 'xls', - 7642 => 'xlu', - 7643 => 'xly', - 7644 => 'xma', - 7645 => 'xmb', - 7646 => 'xmc', - 7647 => 'xmd', - 7648 => 'xme', - 7649 => 'xmf', - 7650 => 'xmg', - 7651 => 'xmh', - 7652 => 'xmj', - 7653 => 'xmk', - 7654 => 'xml', - 7655 => 'xmm', - 7656 => 'xmn', - 7657 => 'xmo', - 7658 => 'xmp', - 7659 => 'xmq', - 7660 => 'xmr', - 7661 => 'xms', - 7662 => 'xmt', - 7663 => 'xmu', - 7664 => 'xmv', - 7665 => 'xmw', - 7666 => 'xmx', - 7667 => 'xmy', - 7668 => 'xmz', - 7669 => 'xna', - 7670 => 'xnb', - 7671 => 'xnd', - 7672 => 'xng', - 7673 => 'xnh', - 7674 => 'xni', - 7675 => 'xnj', - 7676 => 'xnk', - 7677 => 'xnm', - 7678 => 'xnn', - 7679 => 'xno', - 7680 => 'xnq', - 7681 => 'xnr', - 7682 => 'xns', - 7683 => 'xnt', - 7684 => 'xnu', - 7685 => 'xny', - 7686 => 'xnz', - 7687 => 'xoc', - 7688 => 'xod', - 7689 => 'xog', - 7690 => 'xoi', - 7691 => 'xok', - 7692 => 'xom', - 7693 => 'xon', - 7694 => 'xoo', - 7695 => 'xop', - 7696 => 'xor', - 7697 => 'xow', - 7698 => 'xpa', - 7699 => 'xpb', - 7700 => 'xpc', - 7701 => 'xpd', - 7702 => 'xpe', - 7703 => 'xpf', - 7704 => 'xpg', - 7705 => 'xph', - 7706 => 'xpi', - 7707 => 'xpj', - 7708 => 'xpk', - 7709 => 'xpl', - 7710 => 'xpm', - 7711 => 'xpn', - 7712 => 'xpo', - 7713 => 'xpp', - 7714 => 'xpq', - 7715 => 'xpr', - 7716 => 'xps', - 7717 => 'xpt', - 7718 => 'xpu', - 7719 => 'xpv', - 7720 => 'xpw', - 7721 => 'xpx', - 7722 => 'xpy', - 7723 => 'xpz', - 7724 => 'xqa', - 7725 => 'xqt', - 7726 => 'xra', - 7727 => 'xrb', - 7728 => 'xrd', - 7729 => 'xre', - 7730 => 'xrg', - 7731 => 'xri', - 7732 => 'xrm', - 7733 => 'xrn', - 7734 => 'xrq', - 7735 => 'xrr', - 7736 => 'xrt', - 7737 => 'xru', - 7738 => 'xrw', - 7739 => 'xsa', - 7740 => 'xsb', - 7741 => 'xsc', - 7742 => 'xsd', - 7743 => 'xse', - 7744 => 'xsh', - 7745 => 'xsi', - 7746 => 'xsj', - 7747 => 'xsl', - 7748 => 'xsm', - 7749 => 'xsn', - 7750 => 'xso', - 7751 => 'xsp', - 7752 => 'xsq', - 7753 => 'xsr', - 7754 => 'xss', - 7755 => 'xsu', - 7756 => 'xsv', - 7757 => 'xsy', - 7758 => 'xta', - 7759 => 'xtb', - 7760 => 'xtc', - 7761 => 'xtd', - 7762 => 'xte', - 7763 => 'xtg', - 7764 => 'xth', - 7765 => 'xti', - 7766 => 'xtj', - 7767 => 'xtl', - 7768 => 'xtm', - 7769 => 'xtn', - 7770 => 'xto', - 7771 => 'xtp', - 7772 => 'xtq', - 7773 => 'xtr', - 7774 => 'xts', - 7775 => 'xtt', - 7776 => 'xtu', - 7777 => 'xtv', - 7778 => 'xtw', - 7779 => 'xty', - 7780 => 'xtz', - 7781 => 'xua', - 7782 => 'xub', - 7783 => 'xud', - 7784 => 'xug', - 7785 => 'xuj', - 7786 => 'xul', - 7787 => 'xum', - 7788 => 'xun', - 7789 => 'xuo', - 7790 => 'xup', - 7791 => 'xur', - 7792 => 'xut', - 7793 => 'xuu', - 7794 => 'xve', - 7795 => 'xvi', - 7796 => 'xvn', - 7797 => 'xvo', - 7798 => 'xvs', - 7799 => 'xwa', - 7800 => 'xwc', - 7801 => 'xwd', - 7802 => 'xwe', - 7803 => 'xwg', - 7804 => 'xwj', - 7805 => 'xwk', - 7806 => 'xwl', - 7807 => 'xwo', - 7808 => 'xwr', - 7809 => 'xwt', - 7810 => 'xww', - 7811 => 'xxb', - 7812 => 'xxk', - 7813 => 'xxm', - 7814 => 'xxr', - 7815 => 'xxt', - 7816 => 'xya', - 7817 => 'xyb', - 7818 => 'xyj', - 7819 => 'xyk', - 7820 => 'xyl', - 7821 => 'xyt', - 7822 => 'xyy', - 7823 => 'xzh', - 7824 => 'xzm', - 7825 => 'xzp', - 7826 => 'yaa', - 7827 => 'yab', - 7828 => 'yac', - 7829 => 'yad', - 7830 => 'yae', - 7831 => 'yaf', - 7832 => 'yag', - 7833 => 'yah', - 7834 => 'yai', - 7835 => 'yaj', - 7836 => 'yak', - 7837 => 'yal', - 7838 => 'yam', - 7839 => 'yan', - 7840 => 'yao', - 7841 => 'yap', - 7842 => 'yaq', - 7843 => 'yar', - 7844 => 'yas', - 7845 => 'yat', - 7846 => 'yau', - 7847 => 'yav', - 7848 => 'yaw', - 7849 => 'yax', - 7850 => 'yay', - 7851 => 'yaz', - 7852 => 'yba', - 7853 => 'ybb', - 7854 => 'ybd', - 7855 => 'ybe', - 7856 => 'ybh', - 7857 => 'ybi', - 7858 => 'ybj', - 7859 => 'ybk', - 7860 => 'ybl', - 7861 => 'ybm', - 7862 => 'ybn', - 7863 => 'ybo', - 7864 => 'ybx', - 7865 => 'yby', - 7866 => 'ych', - 7867 => 'ycl', - 7868 => 'ycn', - 7869 => 'ycp', - 7870 => 'ycr', - 7871 => 'yda', - 7872 => 'ydd', - 7873 => 'yde', - 7874 => 'ydg', - 7875 => 'ydk', - 7876 => 'yds', - 7877 => 'yea', - 7878 => 'yec', - 7879 => 'yee', - 7880 => 'yei', - 7881 => 'yej', - 7882 => 'yel', - 7883 => 'yen', - 7884 => 'yer', - 7885 => 'yes', - 7886 => 'yet', - 7887 => 'yeu', - 7888 => 'yev', - 7889 => 'yey', - 7890 => 'yga', - 7891 => 'ygi', - 7892 => 'ygl', - 7893 => 'ygm', - 7894 => 'ygp', - 7895 => 'ygr', - 7896 => 'ygs', - 7897 => 'ygu', - 7898 => 'ygw', - 7899 => 'yha', - 7900 => 'yhd', - 7901 => 'yhl', - 7902 => 'yhs', - 7903 => 'yi', - 7904 => 'yia', - 7905 => 'yif', - 7906 => 'yig', - 7907 => 'yih', - 7908 => 'yii', - 7909 => 'yij', - 7910 => 'yik', - 7911 => 'yil', - 7912 => 'yim', - 7913 => 'yin', - 7914 => 'yip', - 7915 => 'yiq', - 7916 => 'yir', - 7917 => 'yis', - 7918 => 'yit', - 7919 => 'yiu', - 7920 => 'yiv', - 7921 => 'yix', - 7922 => 'yiy', - 7923 => 'yiz', - 7924 => 'yka', - 7925 => 'ykg', - 7926 => 'ykh', - 7927 => 'yki', - 7928 => 'ykk', - 7929 => 'ykl', - 7930 => 'ykm', - 7931 => 'ykn', - 7932 => 'yko', - 7933 => 'ykr', - 7934 => 'ykt', - 7935 => 'yku', - 7936 => 'yky', - 7937 => 'yla', - 7938 => 'ylb', - 7939 => 'yle', - 7940 => 'ylg', - 7941 => 'yli', - 7942 => 'yll', - 7943 => 'ylm', - 7944 => 'yln', - 7945 => 'ylo', - 7946 => 'ylr', - 7947 => 'ylu', - 7948 => 'yly', - 7949 => 'yma', - 7950 => 'ymb', - 7951 => 'ymc', - 7952 => 'ymd', - 7953 => 'yme', - 7954 => 'ymg', - 7955 => 'ymh', - 7956 => 'ymi', - 7957 => 'ymk', - 7958 => 'yml', - 7959 => 'ymm', - 7960 => 'ymn', - 7961 => 'ymo', - 7962 => 'ymp', - 7963 => 'ymq', - 7964 => 'ymr', - 7965 => 'yms', - 7966 => 'ymt', - 7967 => 'ymx', - 7968 => 'ymz', - 7969 => 'yna', - 7970 => 'ynb', - 7971 => 'ynd', - 7972 => 'yne', - 7973 => 'yng', - 7974 => 'ynh', - 7975 => 'ynk', - 7976 => 'ynl', - 7977 => 'ynn', - 7978 => 'yno', - 7979 => 'ynq', - 7980 => 'yns', - 7981 => 'ynu', - 7982 => 'yo', - 7983 => 'yob', - 7984 => 'yog', - 7985 => 'yoi', - 7986 => 'yok', - 7987 => 'yol', - 7988 => 'yom', - 7989 => 'yon', - 7990 => 'yos', - 7991 => 'yot', - 7992 => 'yox', - 7993 => 'yoy', - 7994 => 'ypa', - 7995 => 'ypb', - 7996 => 'ypg', - 7997 => 'yph', - 7998 => 'ypk', - 7999 => 'ypm', - 8000 => 'ypn', - 8001 => 'ypo', - 8002 => 'ypp', - 8003 => 'ypz', - 8004 => 'yra', - 8005 => 'yrb', - 8006 => 'yre', - 8007 => 'yri', - 8008 => 'yrk', - 8009 => 'yrl', - 8010 => 'yrm', - 8011 => 'yrn', - 8012 => 'yro', - 8013 => 'yrs', - 8014 => 'yrw', - 8015 => 'yry', - 8016 => 'ysc', - 8017 => 'ysd', - 8018 => 'ysg', - 8019 => 'ysl', - 8020 => 'ysm', - 8021 => 'ysn', - 8022 => 'yso', - 8023 => 'ysp', - 8024 => 'ysr', - 8025 => 'yss', - 8026 => 'ysy', - 8027 => 'yta', - 8028 => 'ytl', - 8029 => 'ytp', - 8030 => 'ytw', - 8031 => 'yty', - 8032 => 'yua', - 8033 => 'yub', - 8034 => 'yuc', - 8035 => 'yud', - 8036 => 'yue', - 8037 => 'yuf', - 8038 => 'yug', - 8039 => 'yui', - 8040 => 'yuj', - 8041 => 'yuk', - 8042 => 'yul', - 8043 => 'yum', - 8044 => 'yun', - 8045 => 'yup', - 8046 => 'yuq', - 8047 => 'yur', - 8048 => 'yut', - 8049 => 'yuu', - 8050 => 'yuw', - 8051 => 'yux', - 8052 => 'yuy', - 8053 => 'yuz', - 8054 => 'yva', - 8055 => 'yvt', - 8056 => 'ywa', - 8057 => 'ywg', - 8058 => 'ywl', - 8059 => 'ywn', - 8060 => 'ywq', - 8061 => 'ywr', - 8062 => 'ywt', - 8063 => 'ywu', - 8064 => 'yww', - 8065 => 'yxa', - 8066 => 'yxg', - 8067 => 'yxl', - 8068 => 'yxm', - 8069 => 'yxu', - 8070 => 'yxy', - 8071 => 'yyr', - 8072 => 'yyu', - 8073 => 'yyz', - 8074 => 'yzg', - 8075 => 'yzk', - 8076 => 'za', - 8077 => 'zaa', - 8078 => 'zab', - 8079 => 'zac', - 8080 => 'zad', - 8081 => 'zae', - 8082 => 'zaf', - 8083 => 'zag', - 8084 => 'zah', - 8085 => 'zai', - 8086 => 'zaj', - 8087 => 'zak', - 8088 => 'zal', - 8089 => 'zam', - 8090 => 'zao', - 8091 => 'zap', - 8092 => 'zaq', - 8093 => 'zar', - 8094 => 'zas', - 8095 => 'zat', - 8096 => 'zau', - 8097 => 'zav', - 8098 => 'zaw', - 8099 => 'zax', - 8100 => 'zay', - 8101 => 'zaz', - 8102 => 'zba', - 8103 => 'zbc', - 8104 => 'zbe', - 8105 => 'zbl', - 8106 => 'zbt', - 8107 => 'zbu', - 8108 => 'zbw', - 8109 => 'zca', - 8110 => 'zcd', - 8111 => 'zch', - 8112 => 'zdj', - 8113 => 'zea', - 8114 => 'zeg', - 8115 => 'zeh', - 8116 => 'zem', - 8117 => 'zen', - 8118 => 'zga', - 8119 => 'zgb', - 8120 => 'zgh', - 8121 => 'zgm', - 8122 => 'zgn', - 8123 => 'zgr', - 8124 => 'zh', - 8125 => 'zhb', - 8126 => 'zhd', - 8127 => 'zhi', - 8128 => 'zhn', - 8129 => 'zhw', - 8130 => 'zhx', - 8131 => 'zia', - 8132 => 'zib', - 8133 => 'zik', - 8134 => 'zil', - 8135 => 'zim', - 8136 => 'zin', - 8137 => 'zir', - 8138 => 'ziw', - 8139 => 'ziz', - 8140 => 'zka', - 8141 => 'zkb', - 8142 => 'zkd', - 8143 => 'zkg', - 8144 => 'zkh', - 8145 => 'zkk', - 8146 => 'zkn', - 8147 => 'zko', - 8148 => 'zkp', - 8149 => 'zkr', - 8150 => 'zkt', - 8151 => 'zku', - 8152 => 'zkv', - 8153 => 'zkz', - 8154 => 'zla', - 8155 => 'zle', - 8156 => 'zlj', - 8157 => 'zlm', - 8158 => 'zln', - 8159 => 'zlq', - 8160 => 'zls', - 8161 => 'zlu', - 8162 => 'zlw', - 8163 => 'zma', - 8164 => 'zmb', - 8165 => 'zmc', - 8166 => 'zmd', - 8167 => 'zme', - 8168 => 'zmf', - 8169 => 'zmg', - 8170 => 'zmh', - 8171 => 'zmi', - 8172 => 'zmj', - 8173 => 'zmk', - 8174 => 'zml', - 8175 => 'zmm', - 8176 => 'zmn', - 8177 => 'zmo', - 8178 => 'zmp', - 8179 => 'zmq', - 8180 => 'zmr', - 8181 => 'zms', - 8182 => 'zmt', - 8183 => 'zmu', - 8184 => 'zmv', - 8185 => 'zmw', - 8186 => 'zmx', - 8187 => 'zmy', - 8188 => 'zmz', - 8189 => 'zna', - 8190 => 'znd', - 8191 => 'zne', - 8192 => 'zng', - 8193 => 'znk', - 8194 => 'zns', - 8195 => 'zoc', - 8196 => 'zoh', - 8197 => 'zom', - 8198 => 'zoo', - 8199 => 'zoq', - 8200 => 'zor', - 8201 => 'zos', - 8202 => 'zpa', - 8203 => 'zpb', - 8204 => 'zpc', - 8205 => 'zpd', - 8206 => 'zpe', - 8207 => 'zpf', - 8208 => 'zpg', - 8209 => 'zph', - 8210 => 'zpi', - 8211 => 'zpj', - 8212 => 'zpk', - 8213 => 'zpl', - 8214 => 'zpm', - 8215 => 'zpn', - 8216 => 'zpo', - 8217 => 'zpp', - 8218 => 'zpq', - 8219 => 'zpr', - 8220 => 'zps', - 8221 => 'zpt', - 8222 => 'zpu', - 8223 => 'zpv', - 8224 => 'zpw', - 8225 => 'zpx', - 8226 => 'zpy', - 8227 => 'zpz', - 8228 => 'zqe', - 8229 => 'zra', - 8230 => 'zrg', - 8231 => 'zrn', - 8232 => 'zro', - 8233 => 'zrp', - 8234 => 'zrs', - 8235 => 'zsa', - 8236 => 'zsk', - 8237 => 'zsl', - 8238 => 'zsm', - 8239 => 'zsr', - 8240 => 'zsu', - 8241 => 'zte', - 8242 => 'ztg', - 8243 => 'ztl', - 8244 => 'ztm', - 8245 => 'ztn', - 8246 => 'ztp', - 8247 => 'ztq', - 8248 => 'zts', - 8249 => 'ztt', - 8250 => 'ztu', - 8251 => 'ztx', - 8252 => 'zty', - 8253 => 'zu', - 8254 => 'zua', - 8255 => 'zuh', - 8256 => 'zum', - 8257 => 'zun', - 8258 => 'zuy', - 8259 => 'zwa', - 8260 => 'zxx', - 8261 => 'zyb', - 8262 => 'zyg', - 8263 => 'zyj', - 8264 => 'zyn', - 8265 => 'zyp', - 8266 => 'zza', - 8267 => 'zzj', + 5715 => 'qaa', + 5716 => 'qab', + 5717 => 'qac', + 5718 => 'qad', + 5719 => 'qae', + 5720 => 'qaf', + 5721 => 'qag', + 5722 => 'qah', + 5723 => 'qai', + 5724 => 'qaj', + 5725 => 'qak', + 5726 => 'qal', + 5727 => 'qam', + 5728 => 'qan', + 5729 => 'qao', + 5730 => 'qap', + 5731 => 'qaq', + 5732 => 'qar', + 5733 => 'qas', + 5734 => 'qat', + 5735 => 'qau', + 5736 => 'qav', + 5737 => 'qaw', + 5738 => 'qax', + 5739 => 'qay', + 5740 => 'qaz', + 5741 => 'qba', + 5742 => 'qbb', + 5743 => 'qbc', + 5744 => 'qbd', + 5745 => 'qbe', + 5746 => 'qbf', + 5747 => 'qbg', + 5748 => 'qbh', + 5749 => 'qbi', + 5750 => 'qbj', + 5751 => 'qbk', + 5752 => 'qbl', + 5753 => 'qbm', + 5754 => 'qbn', + 5755 => 'qbo', + 5756 => 'qbp', + 5757 => 'qbq', + 5758 => 'qbr', + 5759 => 'qbs', + 5760 => 'qbt', + 5761 => 'qbu', + 5762 => 'qbv', + 5763 => 'qbw', + 5764 => 'qbx', + 5765 => 'qby', + 5766 => 'qbz', + 5767 => 'qca', + 5768 => 'qcb', + 5769 => 'qcc', + 5770 => 'qcd', + 5771 => 'qce', + 5772 => 'qcf', + 5773 => 'qcg', + 5774 => 'qch', + 5775 => 'qci', + 5776 => 'qcj', + 5777 => 'qck', + 5778 => 'qcl', + 5779 => 'qcm', + 5780 => 'qcn', + 5781 => 'qco', + 5782 => 'qcp', + 5783 => 'qcq', + 5784 => 'qcr', + 5785 => 'qcs', + 5786 => 'qct', + 5787 => 'qcu', + 5788 => 'qcv', + 5789 => 'qcw', + 5790 => 'qcx', + 5791 => 'qcy', + 5792 => 'qcz', + 5793 => 'qda', + 5794 => 'qdb', + 5795 => 'qdc', + 5796 => 'qdd', + 5797 => 'qde', + 5798 => 'qdf', + 5799 => 'qdg', + 5800 => 'qdh', + 5801 => 'qdi', + 5802 => 'qdj', + 5803 => 'qdk', + 5804 => 'qdl', + 5805 => 'qdm', + 5806 => 'qdn', + 5807 => 'qdo', + 5808 => 'qdp', + 5809 => 'qdq', + 5810 => 'qdr', + 5811 => 'qds', + 5812 => 'qdt', + 5813 => 'qdu', + 5814 => 'qdv', + 5815 => 'qdw', + 5816 => 'qdx', + 5817 => 'qdy', + 5818 => 'qdz', + 5819 => 'qea', + 5820 => 'qeb', + 5821 => 'qec', + 5822 => 'qed', + 5823 => 'qee', + 5824 => 'qef', + 5825 => 'qeg', + 5826 => 'qeh', + 5827 => 'qei', + 5828 => 'qej', + 5829 => 'qek', + 5830 => 'qel', + 5831 => 'qem', + 5832 => 'qen', + 5833 => 'qeo', + 5834 => 'qep', + 5835 => 'qeq', + 5836 => 'qer', + 5837 => 'qes', + 5838 => 'qet', + 5839 => 'qeu', + 5840 => 'qev', + 5841 => 'qew', + 5842 => 'qex', + 5843 => 'qey', + 5844 => 'qez', + 5845 => 'qfa', + 5846 => 'qfb', + 5847 => 'qfc', + 5848 => 'qfd', + 5849 => 'qfe', + 5850 => 'qff', + 5851 => 'qfg', + 5852 => 'qfh', + 5853 => 'qfi', + 5854 => 'qfj', + 5855 => 'qfk', + 5856 => 'qfl', + 5857 => 'qfm', + 5858 => 'qfn', + 5859 => 'qfo', + 5860 => 'qfp', + 5861 => 'qfq', + 5862 => 'qfr', + 5863 => 'qfs', + 5864 => 'qft', + 5865 => 'qfu', + 5866 => 'qfv', + 5867 => 'qfw', + 5868 => 'qfx', + 5869 => 'qfy', + 5870 => 'qfz', + 5871 => 'qga', + 5872 => 'qgb', + 5873 => 'qgc', + 5874 => 'qgd', + 5875 => 'qge', + 5876 => 'qgf', + 5877 => 'qgg', + 5878 => 'qgh', + 5879 => 'qgi', + 5880 => 'qgj', + 5881 => 'qgk', + 5882 => 'qgl', + 5883 => 'qgm', + 5884 => 'qgn', + 5885 => 'qgo', + 5886 => 'qgp', + 5887 => 'qgq', + 5888 => 'qgr', + 5889 => 'qgs', + 5890 => 'qgt', + 5891 => 'qgu', + 5892 => 'qgv', + 5893 => 'qgw', + 5894 => 'qgx', + 5895 => 'qgy', + 5896 => 'qgz', + 5897 => 'qha', + 5898 => 'qhb', + 5899 => 'qhc', + 5900 => 'qhd', + 5901 => 'qhe', + 5902 => 'qhf', + 5903 => 'qhg', + 5904 => 'qhh', + 5905 => 'qhi', + 5906 => 'qhj', + 5907 => 'qhk', + 5908 => 'qhl', + 5909 => 'qhm', + 5910 => 'qhn', + 5911 => 'qho', + 5912 => 'qhp', + 5913 => 'qhq', + 5914 => 'qhr', + 5915 => 'qhs', + 5916 => 'qht', + 5917 => 'qhu', + 5918 => 'qhv', + 5919 => 'qhw', + 5920 => 'qhx', + 5921 => 'qhy', + 5922 => 'qhz', + 5923 => 'qia', + 5924 => 'qib', + 5925 => 'qic', + 5926 => 'qid', + 5927 => 'qie', + 5928 => 'qif', + 5929 => 'qig', + 5930 => 'qih', + 5931 => 'qii', + 5932 => 'qij', + 5933 => 'qik', + 5934 => 'qil', + 5935 => 'qim', + 5936 => 'qin', + 5937 => 'qio', + 5938 => 'qip', + 5939 => 'qiq', + 5940 => 'qir', + 5941 => 'qis', + 5942 => 'qit', + 5943 => 'qiu', + 5944 => 'qiv', + 5945 => 'qiw', + 5946 => 'qix', + 5947 => 'qiy', + 5948 => 'qiz', + 5949 => 'qja', + 5950 => 'qjb', + 5951 => 'qjc', + 5952 => 'qjd', + 5953 => 'qje', + 5954 => 'qjf', + 5955 => 'qjg', + 5956 => 'qjh', + 5957 => 'qji', + 5958 => 'qjj', + 5959 => 'qjk', + 5960 => 'qjl', + 5961 => 'qjm', + 5962 => 'qjn', + 5963 => 'qjo', + 5964 => 'qjp', + 5965 => 'qjq', + 5966 => 'qjr', + 5967 => 'qjs', + 5968 => 'qjt', + 5969 => 'qju', + 5970 => 'qjv', + 5971 => 'qjw', + 5972 => 'qjx', + 5973 => 'qjy', + 5974 => 'qjz', + 5975 => 'qka', + 5976 => 'qkb', + 5977 => 'qkc', + 5978 => 'qkd', + 5979 => 'qke', + 5980 => 'qkf', + 5981 => 'qkg', + 5982 => 'qkh', + 5983 => 'qki', + 5984 => 'qkj', + 5985 => 'qkk', + 5986 => 'qkl', + 5987 => 'qkm', + 5988 => 'qkn', + 5989 => 'qko', + 5990 => 'qkp', + 5991 => 'qkq', + 5992 => 'qkr', + 5993 => 'qks', + 5994 => 'qkt', + 5995 => 'qku', + 5996 => 'qkv', + 5997 => 'qkw', + 5998 => 'qkx', + 5999 => 'qky', + 6000 => 'qkz', + 6001 => 'qla', + 6002 => 'qlb', + 6003 => 'qlc', + 6004 => 'qld', + 6005 => 'qle', + 6006 => 'qlf', + 6007 => 'qlg', + 6008 => 'qlh', + 6009 => 'qli', + 6010 => 'qlj', + 6011 => 'qlk', + 6012 => 'qll', + 6013 => 'qlm', + 6014 => 'qln', + 6015 => 'qlo', + 6016 => 'qlp', + 6017 => 'qlq', + 6018 => 'qlr', + 6019 => 'qls', + 6020 => 'qlt', + 6021 => 'qlu', + 6022 => 'qlv', + 6023 => 'qlw', + 6024 => 'qlx', + 6025 => 'qly', + 6026 => 'qlz', + 6027 => 'qma', + 6028 => 'qmb', + 6029 => 'qmc', + 6030 => 'qmd', + 6031 => 'qme', + 6032 => 'qmf', + 6033 => 'qmg', + 6034 => 'qmh', + 6035 => 'qmi', + 6036 => 'qmj', + 6037 => 'qmk', + 6038 => 'qml', + 6039 => 'qmm', + 6040 => 'qmn', + 6041 => 'qmo', + 6042 => 'qmp', + 6043 => 'qmq', + 6044 => 'qmr', + 6045 => 'qms', + 6046 => 'qmt', + 6047 => 'qmu', + 6048 => 'qmv', + 6049 => 'qmw', + 6050 => 'qmx', + 6051 => 'qmy', + 6052 => 'qmz', + 6053 => 'qna', + 6054 => 'qnb', + 6055 => 'qnc', + 6056 => 'qnd', + 6057 => 'qne', + 6058 => 'qnf', + 6059 => 'qng', + 6060 => 'qnh', + 6061 => 'qni', + 6062 => 'qnj', + 6063 => 'qnk', + 6064 => 'qnl', + 6065 => 'qnm', + 6066 => 'qnn', + 6067 => 'qno', + 6068 => 'qnp', + 6069 => 'qnq', + 6070 => 'qnr', + 6071 => 'qns', + 6072 => 'qnt', + 6073 => 'qnu', + 6074 => 'qnv', + 6075 => 'qnw', + 6076 => 'qnx', + 6077 => 'qny', + 6078 => 'qnz', + 6079 => 'qoa', + 6080 => 'qob', + 6081 => 'qoc', + 6082 => 'qod', + 6083 => 'qoe', + 6084 => 'qof', + 6085 => 'qog', + 6086 => 'qoh', + 6087 => 'qoi', + 6088 => 'qoj', + 6089 => 'qok', + 6090 => 'qol', + 6091 => 'qom', + 6092 => 'qon', + 6093 => 'qoo', + 6094 => 'qop', + 6095 => 'qoq', + 6096 => 'qor', + 6097 => 'qos', + 6098 => 'qot', + 6099 => 'qou', + 6100 => 'qov', + 6101 => 'qow', + 6102 => 'qox', + 6103 => 'qoy', + 6104 => 'qoz', + 6105 => 'qpa', + 6106 => 'qpb', + 6107 => 'qpc', + 6108 => 'qpd', + 6109 => 'qpe', + 6110 => 'qpf', + 6111 => 'qpg', + 6112 => 'qph', + 6113 => 'qpi', + 6114 => 'qpj', + 6115 => 'qpk', + 6116 => 'qpl', + 6117 => 'qpm', + 6118 => 'qpn', + 6119 => 'qpo', + 6120 => 'qpp', + 6121 => 'qpq', + 6122 => 'qpr', + 6123 => 'qps', + 6124 => 'qpt', + 6125 => 'qpu', + 6126 => 'qpv', + 6127 => 'qpw', + 6128 => 'qpx', + 6129 => 'qpy', + 6130 => 'qpz', + 6131 => 'qqa', + 6132 => 'qqb', + 6133 => 'qqc', + 6134 => 'qqd', + 6135 => 'qqe', + 6136 => 'qqf', + 6137 => 'qqg', + 6138 => 'qqh', + 6139 => 'qqi', + 6140 => 'qqj', + 6141 => 'qqk', + 6142 => 'qql', + 6143 => 'qqm', + 6144 => 'qqn', + 6145 => 'qqo', + 6146 => 'qqp', + 6147 => 'qqq', + 6148 => 'qqr', + 6149 => 'qqs', + 6150 => 'qqt', + 6151 => 'qqu', + 6152 => 'qqv', + 6153 => 'qqw', + 6154 => 'qqx', + 6155 => 'qqy', + 6156 => 'qqz', + 6157 => 'qra', + 6158 => 'qrb', + 6159 => 'qrc', + 6160 => 'qrd', + 6161 => 'qre', + 6162 => 'qrf', + 6163 => 'qrg', + 6164 => 'qrh', + 6165 => 'qri', + 6166 => 'qrj', + 6167 => 'qrk', + 6168 => 'qrl', + 6169 => 'qrm', + 6170 => 'qrn', + 6171 => 'qro', + 6172 => 'qrp', + 6173 => 'qrq', + 6174 => 'qrr', + 6175 => 'qrs', + 6176 => 'qrt', + 6177 => 'qru', + 6178 => 'qrv', + 6179 => 'qrw', + 6180 => 'qrx', + 6181 => 'qry', + 6182 => 'qrz', + 6183 => 'qsa', + 6184 => 'qsb', + 6185 => 'qsc', + 6186 => 'qsd', + 6187 => 'qse', + 6188 => 'qsf', + 6189 => 'qsg', + 6190 => 'qsh', + 6191 => 'qsi', + 6192 => 'qsj', + 6193 => 'qsk', + 6194 => 'qsl', + 6195 => 'qsm', + 6196 => 'qsn', + 6197 => 'qso', + 6198 => 'qsp', + 6199 => 'qsq', + 6200 => 'qsr', + 6201 => 'qss', + 6202 => 'qst', + 6203 => 'qsu', + 6204 => 'qsv', + 6205 => 'qsw', + 6206 => 'qsx', + 6207 => 'qsy', + 6208 => 'qsz', + 6209 => 'qta', + 6210 => 'qtb', + 6211 => 'qtc', + 6212 => 'qtd', + 6213 => 'qte', + 6214 => 'qtf', + 6215 => 'qtg', + 6216 => 'qth', + 6217 => 'qti', + 6218 => 'qtj', + 6219 => 'qtk', + 6220 => 'qtl', + 6221 => 'qtm', + 6222 => 'qtn', + 6223 => 'qto', + 6224 => 'qtp', + 6225 => 'qtq', + 6226 => 'qtr', + 6227 => 'qts', + 6228 => 'qtt', + 6229 => 'qtu', + 6230 => 'qtv', + 6231 => 'qtw', + 6232 => 'qtx', + 6233 => 'qty', + 6234 => 'qtz', + 6235 => 'qu', + 6236 => 'qua', + 6237 => 'qub', + 6238 => 'quc', + 6239 => 'qud', + 6240 => 'quf', + 6241 => 'qug', + 6242 => 'quh', + 6243 => 'qui', + 6244 => 'quk', + 6245 => 'qul', + 6246 => 'qum', + 6247 => 'qun', + 6248 => 'qup', + 6249 => 'quq', + 6250 => 'qur', + 6251 => 'qus', + 6252 => 'quv', + 6253 => 'quw', + 6254 => 'qux', + 6255 => 'quy', + 6256 => 'quz', + 6257 => 'qva', + 6258 => 'qvc', + 6259 => 'qve', + 6260 => 'qvh', + 6261 => 'qvi', + 6262 => 'qvj', + 6263 => 'qvl', + 6264 => 'qvm', + 6265 => 'qvn', + 6266 => 'qvo', + 6267 => 'qvp', + 6268 => 'qvs', + 6269 => 'qvw', + 6270 => 'qvy', + 6271 => 'qvz', + 6272 => 'qwa', + 6273 => 'qwc', + 6274 => 'qwe', + 6275 => 'qwh', + 6276 => 'qwm', + 6277 => 'qws', + 6278 => 'qwt', + 6279 => 'qxa', + 6280 => 'qxc', + 6281 => 'qxh', + 6282 => 'qxl', + 6283 => 'qxn', + 6284 => 'qxo', + 6285 => 'qxp', + 6286 => 'qxq', + 6287 => 'qxr', + 6288 => 'qxs', + 6289 => 'qxt', + 6290 => 'qxu', + 6291 => 'qxw', + 6292 => 'qya', + 6293 => 'qyp', + 6294 => 'raa', + 6295 => 'rab', + 6296 => 'rac', + 6297 => 'rad', + 6298 => 'raf', + 6299 => 'rag', + 6300 => 'rah', + 6301 => 'rai', + 6302 => 'raj', + 6303 => 'rak', + 6304 => 'ral', + 6305 => 'ram', + 6306 => 'ran', + 6307 => 'rao', + 6308 => 'rap', + 6309 => 'raq', + 6310 => 'rar', + 6311 => 'ras', + 6312 => 'rat', + 6313 => 'rau', + 6314 => 'rav', + 6315 => 'raw', + 6316 => 'rax', + 6317 => 'ray', + 6318 => 'raz', + 6319 => 'rbb', + 6320 => 'rbk', + 6321 => 'rbl', + 6322 => 'rbp', + 6323 => 'rcf', + 6324 => 'rdb', + 6325 => 'rea', + 6326 => 'reb', + 6327 => 'ree', + 6328 => 'reg', + 6329 => 'rei', + 6330 => 'rej', + 6331 => 'rel', + 6332 => 'rem', + 6333 => 'ren', + 6334 => 'rer', + 6335 => 'res', + 6336 => 'ret', + 6337 => 'rey', + 6338 => 'rga', + 6339 => 'rge', + 6340 => 'rgk', + 6341 => 'rgn', + 6342 => 'rgr', + 6343 => 'rgs', + 6344 => 'rgu', + 6345 => 'rhg', + 6346 => 'rhp', + 6347 => 'ria', + 6348 => 'rib', + 6349 => 'rie', + 6350 => 'rif', + 6351 => 'ril', + 6352 => 'rim', + 6353 => 'rin', + 6354 => 'rir', + 6355 => 'rit', + 6356 => 'riu', + 6357 => 'rjg', + 6358 => 'rji', + 6359 => 'rjs', + 6360 => 'rka', + 6361 => 'rkb', + 6362 => 'rkh', + 6363 => 'rki', + 6364 => 'rkm', + 6365 => 'rkt', + 6366 => 'rkw', + 6367 => 'rm', + 6368 => 'rma', + 6369 => 'rmb', + 6370 => 'rmc', + 6371 => 'rmd', + 6372 => 'rme', + 6373 => 'rmf', + 6374 => 'rmg', + 6375 => 'rmh', + 6376 => 'rmi', + 6377 => 'rmk', + 6378 => 'rml', + 6379 => 'rmm', + 6380 => 'rmn', + 6381 => 'rmo', + 6382 => 'rmp', + 6383 => 'rmq', + 6384 => 'rmr', + 6385 => 'rms', + 6386 => 'rmt', + 6387 => 'rmu', + 6388 => 'rmv', + 6389 => 'rmw', + 6390 => 'rmx', + 6391 => 'rmy', + 6392 => 'rmz', + 6393 => 'rn', + 6394 => 'rna', + 6395 => 'rnb', + 6396 => 'rnd', + 6397 => 'rng', + 6398 => 'rnl', + 6399 => 'rnn', + 6400 => 'rnp', + 6401 => 'rnr', + 6402 => 'rnw', + 6403 => 'ro', + 6404 => 'roa', + 6405 => 'rob', + 6406 => 'roc', + 6407 => 'rod', + 6408 => 'roe', + 6409 => 'rof', + 6410 => 'rog', + 6411 => 'rol', + 6412 => 'rom', + 6413 => 'roo', + 6414 => 'rop', + 6415 => 'ror', + 6416 => 'rou', + 6417 => 'row', + 6418 => 'rpn', + 6419 => 'rpt', + 6420 => 'rri', + 6421 => 'rrm', + 6422 => 'rro', + 6423 => 'rrt', + 6424 => 'rsb', + 6425 => 'rsi', + 6426 => 'rsk', + 6427 => 'rsl', + 6428 => 'rsm', + 6429 => 'rsn', + 6430 => 'rsw', + 6431 => 'rtc', + 6432 => 'rth', + 6433 => 'rtm', + 6434 => 'rts', + 6435 => 'rtw', + 6436 => 'ru', + 6437 => 'rub', + 6438 => 'ruc', + 6439 => 'rue', + 6440 => 'ruf', + 6441 => 'rug', + 6442 => 'ruh', + 6443 => 'rui', + 6444 => 'ruk', + 6445 => 'ruo', + 6446 => 'rup', + 6447 => 'ruq', + 6448 => 'rut', + 6449 => 'ruu', + 6450 => 'ruy', + 6451 => 'ruz', + 6452 => 'rw', + 6453 => 'rwa', + 6454 => 'rwk', + 6455 => 'rwl', + 6456 => 'rwm', + 6457 => 'rwo', + 6458 => 'rwr', + 6459 => 'rxd', + 6460 => 'rxw', + 6461 => 'ryn', + 6462 => 'rys', + 6463 => 'ryu', + 6464 => 'rzh', + 6465 => 'sa', + 6466 => 'saa', + 6467 => 'sab', + 6468 => 'sac', + 6469 => 'sad', + 6470 => 'sae', + 6471 => 'saf', + 6472 => 'sah', + 6473 => 'sai', + 6474 => 'saj', + 6475 => 'sak', + 6476 => 'sal', + 6477 => 'sam', + 6478 => 'sao', + 6479 => 'sap', + 6480 => 'saq', + 6481 => 'sar', + 6482 => 'sas', + 6483 => 'sat', + 6484 => 'sau', + 6485 => 'sav', + 6486 => 'saw', + 6487 => 'sax', + 6488 => 'say', + 6489 => 'saz', + 6490 => 'sba', + 6491 => 'sbb', + 6492 => 'sbc', + 6493 => 'sbd', + 6494 => 'sbe', + 6495 => 'sbf', + 6496 => 'sbg', + 6497 => 'sbh', + 6498 => 'sbi', + 6499 => 'sbj', + 6500 => 'sbk', + 6501 => 'sbl', + 6502 => 'sbm', + 6503 => 'sbn', + 6504 => 'sbo', + 6505 => 'sbp', + 6506 => 'sbq', + 6507 => 'sbr', + 6508 => 'sbs', + 6509 => 'sbt', + 6510 => 'sbu', + 6511 => 'sbv', + 6512 => 'sbw', + 6513 => 'sbx', + 6514 => 'sby', + 6515 => 'sbz', + 6516 => 'sc', + 6517 => 'sca', + 6518 => 'scb', + 6519 => 'sce', + 6520 => 'scf', + 6521 => 'scg', + 6522 => 'sch', + 6523 => 'sci', + 6524 => 'sck', + 6525 => 'scl', + 6526 => 'scn', + 6527 => 'sco', + 6528 => 'scp', + 6529 => 'scq', + 6530 => 'scs', + 6531 => 'sct', + 6532 => 'scu', + 6533 => 'scv', + 6534 => 'scw', + 6535 => 'scx', + 6536 => 'sd', + 6537 => 'sda', + 6538 => 'sdb', + 6539 => 'sdc', + 6540 => 'sde', + 6541 => 'sdf', + 6542 => 'sdg', + 6543 => 'sdh', + 6544 => 'sdj', + 6545 => 'sdk', + 6546 => 'sdl', + 6547 => 'sdm', + 6548 => 'sdn', + 6549 => 'sdo', + 6550 => 'sdp', + 6551 => 'sdq', + 6552 => 'sdr', + 6553 => 'sds', + 6554 => 'sdt', + 6555 => 'sdu', + 6556 => 'sdv', + 6557 => 'sdx', + 6558 => 'sdz', + 6559 => 'se', + 6560 => 'sea', + 6561 => 'seb', + 6562 => 'sec', + 6563 => 'sed', + 6564 => 'see', + 6565 => 'sef', + 6566 => 'seg', + 6567 => 'seh', + 6568 => 'sei', + 6569 => 'sej', + 6570 => 'sek', + 6571 => 'sel', + 6572 => 'sem', + 6573 => 'sen', + 6574 => 'seo', + 6575 => 'sep', + 6576 => 'seq', + 6577 => 'ser', + 6578 => 'ses', + 6579 => 'set', + 6580 => 'seu', + 6581 => 'sev', + 6582 => 'sew', + 6583 => 'sey', + 6584 => 'sez', + 6585 => 'sfb', + 6586 => 'sfe', + 6587 => 'sfm', + 6588 => 'sfs', + 6589 => 'sfw', + 6590 => 'sg', + 6591 => 'sga', + 6592 => 'sgb', + 6593 => 'sgc', + 6594 => 'sgd', + 6595 => 'sge', + 6596 => 'sgg', + 6597 => 'sgh', + 6598 => 'sgi', + 6599 => 'sgj', + 6600 => 'sgk', + 6601 => 'sgl', + 6602 => 'sgm', + 6603 => 'sgn', + 6604 => 'sgo', + 6605 => 'sgp', + 6606 => 'sgr', + 6607 => 'sgs', + 6608 => 'sgt', + 6609 => 'sgu', + 6610 => 'sgw', + 6611 => 'sgx', + 6612 => 'sgy', + 6613 => 'sgz', + 6614 => 'sh', + 6615 => 'sha', + 6616 => 'shb', + 6617 => 'shc', + 6618 => 'shd', + 6619 => 'she', + 6620 => 'shg', + 6621 => 'shh', + 6622 => 'shi', + 6623 => 'shj', + 6624 => 'shk', + 6625 => 'shl', + 6626 => 'shm', + 6627 => 'shn', + 6628 => 'sho', + 6629 => 'shp', + 6630 => 'shq', + 6631 => 'shr', + 6632 => 'shs', + 6633 => 'sht', + 6634 => 'shu', + 6635 => 'shv', + 6636 => 'shw', + 6637 => 'shx', + 6638 => 'shy', + 6639 => 'shz', + 6640 => 'si', + 6641 => 'sia', + 6642 => 'sib', + 6643 => 'sid', + 6644 => 'sie', + 6645 => 'sif', + 6646 => 'sig', + 6647 => 'sih', + 6648 => 'sii', + 6649 => 'sij', + 6650 => 'sik', + 6651 => 'sil', + 6652 => 'sim', + 6653 => 'sio', + 6654 => 'sip', + 6655 => 'siq', + 6656 => 'sir', + 6657 => 'sis', + 6658 => 'sit', + 6659 => 'siu', + 6660 => 'siv', + 6661 => 'siw', + 6662 => 'six', + 6663 => 'siy', + 6664 => 'siz', + 6665 => 'sja', + 6666 => 'sjb', + 6667 => 'sjc', + 6668 => 'sjd', + 6669 => 'sje', + 6670 => 'sjg', + 6671 => 'sjk', + 6672 => 'sjl', + 6673 => 'sjm', + 6674 => 'sjn', + 6675 => 'sjo', + 6676 => 'sjp', + 6677 => 'sjr', + 6678 => 'sjs', + 6679 => 'sjt', + 6680 => 'sju', + 6681 => 'sjw', + 6682 => 'sk', + 6683 => 'ska', + 6684 => 'skb', + 6685 => 'skc', + 6686 => 'skd', + 6687 => 'ske', + 6688 => 'skf', + 6689 => 'skg', + 6690 => 'skh', + 6691 => 'ski', + 6692 => 'skj', + 6693 => 'skk', + 6694 => 'skm', + 6695 => 'skn', + 6696 => 'sko', + 6697 => 'skp', + 6698 => 'skq', + 6699 => 'skr', + 6700 => 'sks', + 6701 => 'skt', + 6702 => 'sku', + 6703 => 'skv', + 6704 => 'skw', + 6705 => 'skx', + 6706 => 'sky', + 6707 => 'skz', + 6708 => 'sl', + 6709 => 'sla', + 6710 => 'slc', + 6711 => 'sld', + 6712 => 'sle', + 6713 => 'slf', + 6714 => 'slg', + 6715 => 'slh', + 6716 => 'sli', + 6717 => 'slj', + 6718 => 'sll', + 6719 => 'slm', + 6720 => 'sln', + 6721 => 'slp', + 6722 => 'slq', + 6723 => 'slr', + 6724 => 'sls', + 6725 => 'slt', + 6726 => 'slu', + 6727 => 'slw', + 6728 => 'slx', + 6729 => 'sly', + 6730 => 'slz', + 6731 => 'sm', + 6732 => 'sma', + 6733 => 'smb', + 6734 => 'smc', + 6735 => 'smd', + 6736 => 'smf', + 6737 => 'smg', + 6738 => 'smh', + 6739 => 'smi', + 6740 => 'smj', + 6741 => 'smk', + 6742 => 'sml', + 6743 => 'smm', + 6744 => 'smn', + 6745 => 'smp', + 6746 => 'smq', + 6747 => 'smr', + 6748 => 'sms', + 6749 => 'smt', + 6750 => 'smu', + 6751 => 'smv', + 6752 => 'smw', + 6753 => 'smx', + 6754 => 'smy', + 6755 => 'smz', + 6756 => 'sn', + 6757 => 'snb', + 6758 => 'snc', + 6759 => 'sne', + 6760 => 'snf', + 6761 => 'sng', + 6762 => 'snh', + 6763 => 'sni', + 6764 => 'snj', + 6765 => 'snk', + 6766 => 'snl', + 6767 => 'snm', + 6768 => 'snn', + 6769 => 'sno', + 6770 => 'snp', + 6771 => 'snq', + 6772 => 'snr', + 6773 => 'sns', + 6774 => 'snu', + 6775 => 'snv', + 6776 => 'snw', + 6777 => 'snx', + 6778 => 'sny', + 6779 => 'snz', + 6780 => 'so', + 6781 => 'soa', + 6782 => 'sob', + 6783 => 'soc', + 6784 => 'sod', + 6785 => 'soe', + 6786 => 'sog', + 6787 => 'soh', + 6788 => 'soi', + 6789 => 'soj', + 6790 => 'sok', + 6791 => 'sol', + 6792 => 'son', + 6793 => 'soo', + 6794 => 'sop', + 6795 => 'soq', + 6796 => 'sor', + 6797 => 'sos', + 6798 => 'sou', + 6799 => 'sov', + 6800 => 'sow', + 6801 => 'sox', + 6802 => 'soy', + 6803 => 'soz', + 6804 => 'spb', + 6805 => 'spc', + 6806 => 'spd', + 6807 => 'spe', + 6808 => 'spg', + 6809 => 'spi', + 6810 => 'spk', + 6811 => 'spl', + 6812 => 'spm', + 6813 => 'spn', + 6814 => 'spo', + 6815 => 'spp', + 6816 => 'spq', + 6817 => 'spr', + 6818 => 'sps', + 6819 => 'spt', + 6820 => 'spu', + 6821 => 'spv', + 6822 => 'spx', + 6823 => 'spy', + 6824 => 'sq', + 6825 => 'sqa', + 6826 => 'sqh', + 6827 => 'sqj', + 6828 => 'sqk', + 6829 => 'sqm', + 6830 => 'sqn', + 6831 => 'sqo', + 6832 => 'sqq', + 6833 => 'sqr', + 6834 => 'sqs', + 6835 => 'sqt', + 6836 => 'squ', + 6837 => 'sqx', + 6838 => 'sr', + 6839 => 'sra', + 6840 => 'srb', + 6841 => 'src', + 6842 => 'sre', + 6843 => 'srf', + 6844 => 'srg', + 6845 => 'srh', + 6846 => 'sri', + 6847 => 'srk', + 6848 => 'srl', + 6849 => 'srm', + 6850 => 'srn', + 6851 => 'sro', + 6852 => 'srq', + 6853 => 'srr', + 6854 => 'srs', + 6855 => 'srt', + 6856 => 'sru', + 6857 => 'srv', + 6858 => 'srw', + 6859 => 'srx', + 6860 => 'sry', + 6861 => 'srz', + 6862 => 'ss', + 6863 => 'ssa', + 6864 => 'ssb', + 6865 => 'ssc', + 6866 => 'ssd', + 6867 => 'sse', + 6868 => 'ssf', + 6869 => 'ssg', + 6870 => 'ssh', + 6871 => 'ssi', + 6872 => 'ssj', + 6873 => 'ssk', + 6874 => 'ssl', + 6875 => 'ssm', + 6876 => 'ssn', + 6877 => 'sso', + 6878 => 'ssp', + 6879 => 'ssq', + 6880 => 'ssr', + 6881 => 'sss', + 6882 => 'sst', + 6883 => 'ssu', + 6884 => 'ssv', + 6885 => 'ssx', + 6886 => 'ssy', + 6887 => 'ssz', + 6888 => 'st', + 6889 => 'sta', + 6890 => 'stb', + 6891 => 'std', + 6892 => 'ste', + 6893 => 'stf', + 6894 => 'stg', + 6895 => 'sth', + 6896 => 'sti', + 6897 => 'stj', + 6898 => 'stk', + 6899 => 'stl', + 6900 => 'stm', + 6901 => 'stn', + 6902 => 'sto', + 6903 => 'stp', + 6904 => 'stq', + 6905 => 'str', + 6906 => 'sts', + 6907 => 'stt', + 6908 => 'stu', + 6909 => 'stv', + 6910 => 'stw', + 6911 => 'sty', + 6912 => 'su', + 6913 => 'sua', + 6914 => 'sub', + 6915 => 'suc', + 6916 => 'sue', + 6917 => 'sug', + 6918 => 'sui', + 6919 => 'suj', + 6920 => 'suk', + 6921 => 'sul', + 6922 => 'sum', + 6923 => 'suo', + 6924 => 'suq', + 6925 => 'sur', + 6926 => 'sus', + 6927 => 'sut', + 6928 => 'suv', + 6929 => 'suw', + 6930 => 'sux', + 6931 => 'suy', + 6932 => 'suz', + 6933 => 'sv', + 6934 => 'sva', + 6935 => 'svb', + 6936 => 'svc', + 6937 => 'sve', + 6938 => 'svk', + 6939 => 'svm', + 6940 => 'svr', + 6941 => 'svs', + 6942 => 'svx', + 6943 => 'sw', + 6944 => 'swb', + 6945 => 'swc', + 6946 => 'swf', + 6947 => 'swg', + 6948 => 'swh', + 6949 => 'swi', + 6950 => 'swj', + 6951 => 'swk', + 6952 => 'swl', + 6953 => 'swm', + 6954 => 'swn', + 6955 => 'swo', + 6956 => 'swp', + 6957 => 'swq', + 6958 => 'swr', + 6959 => 'sws', + 6960 => 'swt', + 6961 => 'swu', + 6962 => 'swv', + 6963 => 'sww', + 6964 => 'swx', + 6965 => 'swy', + 6966 => 'sxb', + 6967 => 'sxc', + 6968 => 'sxe', + 6969 => 'sxg', + 6970 => 'sxk', + 6971 => 'sxl', + 6972 => 'sxm', + 6973 => 'sxn', + 6974 => 'sxo', + 6975 => 'sxr', + 6976 => 'sxs', + 6977 => 'sxu', + 6978 => 'sxw', + 6979 => 'sya', + 6980 => 'syb', + 6981 => 'syc', + 6982 => 'syd', + 6983 => 'syi', + 6984 => 'syk', + 6985 => 'syl', + 6986 => 'sym', + 6987 => 'syn', + 6988 => 'syo', + 6989 => 'syr', + 6990 => 'sys', + 6991 => 'syw', + 6992 => 'syx', + 6993 => 'syy', + 6994 => 'sza', + 6995 => 'szb', + 6996 => 'szc', + 6997 => 'szd', + 6998 => 'sze', + 6999 => 'szg', + 7000 => 'szl', + 7001 => 'szn', + 7002 => 'szp', + 7003 => 'szs', + 7004 => 'szv', + 7005 => 'szw', + 7006 => 'szy', + 7007 => 'ta', + 7008 => 'taa', + 7009 => 'tab', + 7010 => 'tac', + 7011 => 'tad', + 7012 => 'tae', + 7013 => 'taf', + 7014 => 'tag', + 7015 => 'tai', + 7016 => 'taj', + 7017 => 'tak', + 7018 => 'tal', + 7019 => 'tan', + 7020 => 'tao', + 7021 => 'tap', + 7022 => 'taq', + 7023 => 'tar', + 7024 => 'tas', + 7025 => 'tau', + 7026 => 'tav', + 7027 => 'taw', + 7028 => 'tax', + 7029 => 'tay', + 7030 => 'taz', + 7031 => 'tba', + 7032 => 'tbb', + 7033 => 'tbc', + 7034 => 'tbd', + 7035 => 'tbe', + 7036 => 'tbf', + 7037 => 'tbg', + 7038 => 'tbh', + 7039 => 'tbi', + 7040 => 'tbj', + 7041 => 'tbk', + 7042 => 'tbl', + 7043 => 'tbm', + 7044 => 'tbn', + 7045 => 'tbo', + 7046 => 'tbp', + 7047 => 'tbq', + 7048 => 'tbr', + 7049 => 'tbs', + 7050 => 'tbt', + 7051 => 'tbu', + 7052 => 'tbv', + 7053 => 'tbw', + 7054 => 'tbx', + 7055 => 'tby', + 7056 => 'tbz', + 7057 => 'tca', + 7058 => 'tcb', + 7059 => 'tcc', + 7060 => 'tcd', + 7061 => 'tce', + 7062 => 'tcf', + 7063 => 'tcg', + 7064 => 'tch', + 7065 => 'tci', + 7066 => 'tck', + 7067 => 'tcl', + 7068 => 'tcm', + 7069 => 'tcn', + 7070 => 'tco', + 7071 => 'tcp', + 7072 => 'tcq', + 7073 => 'tcs', + 7074 => 'tct', + 7075 => 'tcu', + 7076 => 'tcw', + 7077 => 'tcx', + 7078 => 'tcy', + 7079 => 'tcz', + 7080 => 'tda', + 7081 => 'tdb', + 7082 => 'tdc', + 7083 => 'tdd', + 7084 => 'tde', + 7085 => 'tdf', + 7086 => 'tdg', + 7087 => 'tdh', + 7088 => 'tdi', + 7089 => 'tdj', + 7090 => 'tdk', + 7091 => 'tdl', + 7092 => 'tdm', + 7093 => 'tdn', + 7094 => 'tdo', + 7095 => 'tdq', + 7096 => 'tdr', + 7097 => 'tds', + 7098 => 'tdt', + 7099 => 'tdu', + 7100 => 'tdv', + 7101 => 'tdx', + 7102 => 'tdy', + 7103 => 'te', + 7104 => 'tea', + 7105 => 'teb', + 7106 => 'tec', + 7107 => 'ted', + 7108 => 'tee', + 7109 => 'tef', + 7110 => 'teg', + 7111 => 'teh', + 7112 => 'tei', + 7113 => 'tek', + 7114 => 'tem', + 7115 => 'ten', + 7116 => 'teo', + 7117 => 'tep', + 7118 => 'teq', + 7119 => 'ter', + 7120 => 'tes', + 7121 => 'tet', + 7122 => 'teu', + 7123 => 'tev', + 7124 => 'tew', + 7125 => 'tex', + 7126 => 'tey', + 7127 => 'tez', + 7128 => 'tfi', + 7129 => 'tfn', + 7130 => 'tfo', + 7131 => 'tfr', + 7132 => 'tft', + 7133 => 'tg', + 7134 => 'tga', + 7135 => 'tgb', + 7136 => 'tgc', + 7137 => 'tgd', + 7138 => 'tge', + 7139 => 'tgf', + 7140 => 'tgg', + 7141 => 'tgh', + 7142 => 'tgi', + 7143 => 'tgj', + 7144 => 'tgn', + 7145 => 'tgo', + 7146 => 'tgp', + 7147 => 'tgq', + 7148 => 'tgr', + 7149 => 'tgs', + 7150 => 'tgt', + 7151 => 'tgu', + 7152 => 'tgv', + 7153 => 'tgw', + 7154 => 'tgx', + 7155 => 'tgy', + 7156 => 'tgz', + 7157 => 'th', + 7158 => 'thc', + 7159 => 'thd', + 7160 => 'the', + 7161 => 'thf', + 7162 => 'thh', + 7163 => 'thi', + 7164 => 'thk', + 7165 => 'thl', + 7166 => 'thm', + 7167 => 'thn', + 7168 => 'thp', + 7169 => 'thq', + 7170 => 'thr', + 7171 => 'ths', + 7172 => 'tht', + 7173 => 'thu', + 7174 => 'thv', + 7175 => 'thw', + 7176 => 'thx', + 7177 => 'thy', + 7178 => 'thz', + 7179 => 'ti', + 7180 => 'tia', + 7181 => 'tic', + 7182 => 'tid', + 7183 => 'tie', + 7184 => 'tif', + 7185 => 'tig', + 7186 => 'tih', + 7187 => 'tii', + 7188 => 'tij', + 7189 => 'tik', + 7190 => 'til', + 7191 => 'tim', + 7192 => 'tin', + 7193 => 'tio', + 7194 => 'tip', + 7195 => 'tiq', + 7196 => 'tis', + 7197 => 'tit', + 7198 => 'tiu', + 7199 => 'tiv', + 7200 => 'tiw', + 7201 => 'tix', + 7202 => 'tiy', + 7203 => 'tiz', + 7204 => 'tja', + 7205 => 'tjg', + 7206 => 'tji', + 7207 => 'tjj', + 7208 => 'tjl', + 7209 => 'tjm', + 7210 => 'tjn', + 7211 => 'tjo', + 7212 => 'tjp', + 7213 => 'tjs', + 7214 => 'tju', + 7215 => 'tjw', + 7216 => 'tk', + 7217 => 'tka', + 7218 => 'tkb', + 7219 => 'tkd', + 7220 => 'tke', + 7221 => 'tkf', + 7222 => 'tkg', + 7223 => 'tkk', + 7224 => 'tkl', + 7225 => 'tkm', + 7226 => 'tkn', + 7227 => 'tkp', + 7228 => 'tkq', + 7229 => 'tkr', + 7230 => 'tks', + 7231 => 'tkt', + 7232 => 'tku', + 7233 => 'tkv', + 7234 => 'tkw', + 7235 => 'tkx', + 7236 => 'tkz', + 7237 => 'tl', + 7238 => 'tla', + 7239 => 'tlb', + 7240 => 'tlc', + 7241 => 'tld', + 7242 => 'tlf', + 7243 => 'tlg', + 7244 => 'tlh', + 7245 => 'tli', + 7246 => 'tlj', + 7247 => 'tlk', + 7248 => 'tll', + 7249 => 'tlm', + 7250 => 'tln', + 7251 => 'tlo', + 7252 => 'tlp', + 7253 => 'tlq', + 7254 => 'tlr', + 7255 => 'tls', + 7256 => 'tlt', + 7257 => 'tlu', + 7258 => 'tlv', + 7259 => 'tlw', + 7260 => 'tlx', + 7261 => 'tly', + 7262 => 'tma', + 7263 => 'tmb', + 7264 => 'tmc', + 7265 => 'tmd', + 7266 => 'tme', + 7267 => 'tmf', + 7268 => 'tmg', + 7269 => 'tmh', + 7270 => 'tmi', + 7271 => 'tmj', + 7272 => 'tmk', + 7273 => 'tml', + 7274 => 'tmm', + 7275 => 'tmn', + 7276 => 'tmo', + 7277 => 'tmp', + 7278 => 'tmq', + 7279 => 'tmr', + 7280 => 'tms', + 7281 => 'tmt', + 7282 => 'tmu', + 7283 => 'tmv', + 7284 => 'tmw', + 7285 => 'tmy', + 7286 => 'tmz', + 7287 => 'tn', + 7288 => 'tna', + 7289 => 'tnb', + 7290 => 'tnc', + 7291 => 'tnd', + 7292 => 'tne', + 7293 => 'tnf', + 7294 => 'tng', + 7295 => 'tnh', + 7296 => 'tni', + 7297 => 'tnk', + 7298 => 'tnl', + 7299 => 'tnm', + 7300 => 'tnn', + 7301 => 'tno', + 7302 => 'tnp', + 7303 => 'tnq', + 7304 => 'tnr', + 7305 => 'tns', + 7306 => 'tnt', + 7307 => 'tnu', + 7308 => 'tnv', + 7309 => 'tnw', + 7310 => 'tnx', + 7311 => 'tny', + 7312 => 'tnz', + 7313 => 'to', + 7314 => 'tob', + 7315 => 'toc', + 7316 => 'tod', + 7317 => 'toe', + 7318 => 'tof', + 7319 => 'tog', + 7320 => 'toh', + 7321 => 'toi', + 7322 => 'toj', + 7323 => 'tok', + 7324 => 'tol', + 7325 => 'tom', + 7326 => 'too', + 7327 => 'top', + 7328 => 'toq', + 7329 => 'tor', + 7330 => 'tos', + 7331 => 'tou', + 7332 => 'tov', + 7333 => 'tow', + 7334 => 'tox', + 7335 => 'toy', + 7336 => 'toz', + 7337 => 'tpa', + 7338 => 'tpc', + 7339 => 'tpe', + 7340 => 'tpf', + 7341 => 'tpg', + 7342 => 'tpi', + 7343 => 'tpj', + 7344 => 'tpk', + 7345 => 'tpl', + 7346 => 'tpm', + 7347 => 'tpn', + 7348 => 'tpo', + 7349 => 'tpp', + 7350 => 'tpq', + 7351 => 'tpr', + 7352 => 'tpt', + 7353 => 'tpu', + 7354 => 'tpv', + 7355 => 'tpw', + 7356 => 'tpx', + 7357 => 'tpy', + 7358 => 'tpz', + 7359 => 'tqb', + 7360 => 'tql', + 7361 => 'tqm', + 7362 => 'tqn', + 7363 => 'tqo', + 7364 => 'tqp', + 7365 => 'tqq', + 7366 => 'tqr', + 7367 => 'tqt', + 7368 => 'tqu', + 7369 => 'tqw', + 7370 => 'tr', + 7371 => 'tra', + 7372 => 'trb', + 7373 => 'trc', + 7374 => 'trd', + 7375 => 'tre', + 7376 => 'trf', + 7377 => 'trg', + 7378 => 'trh', + 7379 => 'tri', + 7380 => 'trj', + 7381 => 'trk', + 7382 => 'trl', + 7383 => 'trm', + 7384 => 'trn', + 7385 => 'tro', + 7386 => 'trp', + 7387 => 'trq', + 7388 => 'trr', + 7389 => 'trs', + 7390 => 'trt', + 7391 => 'tru', + 7392 => 'trv', + 7393 => 'trw', + 7394 => 'trx', + 7395 => 'try', + 7396 => 'trz', + 7397 => 'ts', + 7398 => 'tsa', + 7399 => 'tsb', + 7400 => 'tsc', + 7401 => 'tsd', + 7402 => 'tse', + 7403 => 'tsf', + 7404 => 'tsg', + 7405 => 'tsh', + 7406 => 'tsi', + 7407 => 'tsj', + 7408 => 'tsk', + 7409 => 'tsl', + 7410 => 'tsm', + 7411 => 'tsp', + 7412 => 'tsq', + 7413 => 'tsr', + 7414 => 'tss', + 7415 => 'tst', + 7416 => 'tsu', + 7417 => 'tsv', + 7418 => 'tsw', + 7419 => 'tsx', + 7420 => 'tsy', + 7421 => 'tsz', + 7422 => 'tt', + 7423 => 'tta', + 7424 => 'ttb', + 7425 => 'ttc', + 7426 => 'ttd', + 7427 => 'tte', + 7428 => 'ttf', + 7429 => 'ttg', + 7430 => 'tth', + 7431 => 'tti', + 7432 => 'ttj', + 7433 => 'ttk', + 7434 => 'ttl', + 7435 => 'ttm', + 7436 => 'ttn', + 7437 => 'tto', + 7438 => 'ttp', + 7439 => 'ttq', + 7440 => 'ttr', + 7441 => 'tts', + 7442 => 'ttt', + 7443 => 'ttu', + 7444 => 'ttv', + 7445 => 'ttw', + 7446 => 'tty', + 7447 => 'ttz', + 7448 => 'tua', + 7449 => 'tub', + 7450 => 'tuc', + 7451 => 'tud', + 7452 => 'tue', + 7453 => 'tuf', + 7454 => 'tug', + 7455 => 'tuh', + 7456 => 'tui', + 7457 => 'tuj', + 7458 => 'tul', + 7459 => 'tum', + 7460 => 'tun', + 7461 => 'tuo', + 7462 => 'tup', + 7463 => 'tuq', + 7464 => 'tus', + 7465 => 'tut', + 7466 => 'tuu', + 7467 => 'tuv', + 7468 => 'tuw', + 7469 => 'tux', + 7470 => 'tuy', + 7471 => 'tuz', + 7472 => 'tva', + 7473 => 'tvd', + 7474 => 'tve', + 7475 => 'tvi', + 7476 => 'tvk', + 7477 => 'tvl', + 7478 => 'tvm', + 7479 => 'tvn', + 7480 => 'tvo', + 7481 => 'tvs', + 7482 => 'tvt', + 7483 => 'tvu', + 7484 => 'tvw', + 7485 => 'tvx', + 7486 => 'tvy', + 7487 => 'tw', + 7488 => 'twa', + 7489 => 'twb', + 7490 => 'twc', + 7491 => 'twd', + 7492 => 'twe', + 7493 => 'twf', + 7494 => 'twg', + 7495 => 'twh', + 7496 => 'twl', + 7497 => 'twm', + 7498 => 'twn', + 7499 => 'two', + 7500 => 'twp', + 7501 => 'twq', + 7502 => 'twr', + 7503 => 'twt', + 7504 => 'twu', + 7505 => 'tww', + 7506 => 'twx', + 7507 => 'twy', + 7508 => 'txa', + 7509 => 'txb', + 7510 => 'txc', + 7511 => 'txe', + 7512 => 'txg', + 7513 => 'txh', + 7514 => 'txi', + 7515 => 'txj', + 7516 => 'txm', + 7517 => 'txn', + 7518 => 'txo', + 7519 => 'txq', + 7520 => 'txr', + 7521 => 'txs', + 7522 => 'txt', + 7523 => 'txu', + 7524 => 'txx', + 7525 => 'txy', + 7526 => 'ty', + 7527 => 'tya', + 7528 => 'tye', + 7529 => 'tyh', + 7530 => 'tyi', + 7531 => 'tyj', + 7532 => 'tyl', + 7533 => 'tyn', + 7534 => 'typ', + 7535 => 'tyr', + 7536 => 'tys', + 7537 => 'tyt', + 7538 => 'tyu', + 7539 => 'tyv', + 7540 => 'tyx', + 7541 => 'tyy', + 7542 => 'tyz', + 7543 => 'tza', + 7544 => 'tzh', + 7545 => 'tzj', + 7546 => 'tzl', + 7547 => 'tzm', + 7548 => 'tzn', + 7549 => 'tzo', + 7550 => 'tzx', + 7551 => 'uam', + 7552 => 'uan', + 7553 => 'uar', + 7554 => 'uba', + 7555 => 'ubi', + 7556 => 'ubl', + 7557 => 'ubr', + 7558 => 'ubu', + 7559 => 'uby', + 7560 => 'uda', + 7561 => 'ude', + 7562 => 'udg', + 7563 => 'udi', + 7564 => 'udj', + 7565 => 'udl', + 7566 => 'udm', + 7567 => 'udu', + 7568 => 'ues', + 7569 => 'ufi', + 7570 => 'ug', + 7571 => 'uga', + 7572 => 'ugb', + 7573 => 'uge', + 7574 => 'ugh', + 7575 => 'ugn', + 7576 => 'ugo', + 7577 => 'ugy', + 7578 => 'uha', + 7579 => 'uhn', + 7580 => 'uis', + 7581 => 'uiv', + 7582 => 'uji', + 7583 => 'uk', + 7584 => 'uka', + 7585 => 'ukg', + 7586 => 'ukh', + 7587 => 'uki', + 7588 => 'ukk', + 7589 => 'ukl', + 7590 => 'ukp', + 7591 => 'ukq', + 7592 => 'uks', + 7593 => 'uku', + 7594 => 'ukv', + 7595 => 'ukw', + 7596 => 'uky', + 7597 => 'ula', + 7598 => 'ulb', + 7599 => 'ulc', + 7600 => 'ule', + 7601 => 'ulf', + 7602 => 'uli', + 7603 => 'ulk', + 7604 => 'ull', + 7605 => 'ulm', + 7606 => 'uln', + 7607 => 'ulu', + 7608 => 'ulw', + 7609 => 'uly', + 7610 => 'uma', + 7611 => 'umb', + 7612 => 'umc', + 7613 => 'umd', + 7614 => 'umg', + 7615 => 'umi', + 7616 => 'umm', + 7617 => 'umn', + 7618 => 'umo', + 7619 => 'ump', + 7620 => 'umr', + 7621 => 'ums', + 7622 => 'umu', + 7623 => 'una', + 7624 => 'und', + 7625 => 'une', + 7626 => 'ung', + 7627 => 'uni', + 7628 => 'unk', + 7629 => 'unm', + 7630 => 'unn', + 7631 => 'unp', + 7632 => 'unr', + 7633 => 'unu', + 7634 => 'unx', + 7635 => 'unz', + 7636 => 'uok', + 7637 => 'uon', + 7638 => 'upi', + 7639 => 'upv', + 7640 => 'ur', + 7641 => 'ura', + 7642 => 'urb', + 7643 => 'urc', + 7644 => 'ure', + 7645 => 'urf', + 7646 => 'urg', + 7647 => 'urh', + 7648 => 'uri', + 7649 => 'urj', + 7650 => 'urk', + 7651 => 'url', + 7652 => 'urm', + 7653 => 'urn', + 7654 => 'uro', + 7655 => 'urp', + 7656 => 'urr', + 7657 => 'urt', + 7658 => 'uru', + 7659 => 'urv', + 7660 => 'urw', + 7661 => 'urx', + 7662 => 'ury', + 7663 => 'urz', + 7664 => 'usa', + 7665 => 'ush', + 7666 => 'usi', + 7667 => 'usk', + 7668 => 'usp', + 7669 => 'uss', + 7670 => 'usu', + 7671 => 'uta', + 7672 => 'ute', + 7673 => 'uth', + 7674 => 'utp', + 7675 => 'utr', + 7676 => 'utu', + 7677 => 'uum', + 7678 => 'uun', + 7679 => 'uur', + 7680 => 'uuu', + 7681 => 'uve', + 7682 => 'uvh', + 7683 => 'uvl', + 7684 => 'uwa', + 7685 => 'uya', + 7686 => 'uz', + 7687 => 'uzn', + 7688 => 'uzs', + 7689 => 'vaa', + 7690 => 'vae', + 7691 => 'vaf', + 7692 => 'vag', + 7693 => 'vah', + 7694 => 'vai', + 7695 => 'vaj', + 7696 => 'val', + 7697 => 'vam', + 7698 => 'van', + 7699 => 'vao', + 7700 => 'vap', + 7701 => 'var', + 7702 => 'vas', + 7703 => 'vau', + 7704 => 'vav', + 7705 => 'vay', + 7706 => 'vbb', + 7707 => 'vbk', + 7708 => 've', + 7709 => 'vec', + 7710 => 'ved', + 7711 => 'vel', + 7712 => 'vem', + 7713 => 'veo', + 7714 => 'vep', + 7715 => 'ver', + 7716 => 'vgr', + 7717 => 'vgt', + 7718 => 'vi', + 7719 => 'vic', + 7720 => 'vid', + 7721 => 'vif', + 7722 => 'vig', + 7723 => 'vil', + 7724 => 'vin', + 7725 => 'vis', + 7726 => 'vit', + 7727 => 'viv', + 7728 => 'vjk', + 7729 => 'vka', + 7730 => 'vki', + 7731 => 'vkj', + 7732 => 'vkk', + 7733 => 'vkl', + 7734 => 'vkm', + 7735 => 'vkn', + 7736 => 'vko', + 7737 => 'vkp', + 7738 => 'vkt', + 7739 => 'vku', + 7740 => 'vkz', + 7741 => 'vlp', + 7742 => 'vls', + 7743 => 'vma', + 7744 => 'vmb', + 7745 => 'vmc', + 7746 => 'vmd', + 7747 => 'vme', + 7748 => 'vmf', + 7749 => 'vmg', + 7750 => 'vmh', + 7751 => 'vmi', + 7752 => 'vmj', + 7753 => 'vmk', + 7754 => 'vml', + 7755 => 'vmm', + 7756 => 'vmp', + 7757 => 'vmq', + 7758 => 'vmr', + 7759 => 'vms', + 7760 => 'vmu', + 7761 => 'vmv', + 7762 => 'vmw', + 7763 => 'vmx', + 7764 => 'vmy', + 7765 => 'vmz', + 7766 => 'vnk', + 7767 => 'vnm', + 7768 => 'vnp', + 7769 => 'vo', + 7770 => 'vor', + 7771 => 'vot', + 7772 => 'vra', + 7773 => 'vro', + 7774 => 'vrs', + 7775 => 'vrt', + 7776 => 'vsi', + 7777 => 'vsl', + 7778 => 'vsn', + 7779 => 'vsv', + 7780 => 'vto', + 7781 => 'vum', + 7782 => 'vun', + 7783 => 'vut', + 7784 => 'vwa', + 7785 => 'wa', + 7786 => 'waa', + 7787 => 'wab', + 7788 => 'wac', + 7789 => 'wad', + 7790 => 'wae', + 7791 => 'waf', + 7792 => 'wag', + 7793 => 'wah', + 7794 => 'wai', + 7795 => 'waj', + 7796 => 'wak', + 7797 => 'wal', + 7798 => 'wam', + 7799 => 'wan', + 7800 => 'wao', + 7801 => 'wap', + 7802 => 'waq', + 7803 => 'war', + 7804 => 'was', + 7805 => 'wat', + 7806 => 'wau', + 7807 => 'wav', + 7808 => 'waw', + 7809 => 'wax', + 7810 => 'way', + 7811 => 'waz', + 7812 => 'wba', + 7813 => 'wbb', + 7814 => 'wbe', + 7815 => 'wbf', + 7816 => 'wbh', + 7817 => 'wbi', + 7818 => 'wbj', + 7819 => 'wbk', + 7820 => 'wbl', + 7821 => 'wbm', + 7822 => 'wbp', + 7823 => 'wbq', + 7824 => 'wbr', + 7825 => 'wbs', + 7826 => 'wbt', + 7827 => 'wbv', + 7828 => 'wbw', + 7829 => 'wca', + 7830 => 'wci', + 7831 => 'wdd', + 7832 => 'wdg', + 7833 => 'wdj', + 7834 => 'wdk', + 7835 => 'wdt', + 7836 => 'wdu', + 7837 => 'wdy', + 7838 => 'wea', + 7839 => 'wec', + 7840 => 'wed', + 7841 => 'weg', + 7842 => 'weh', + 7843 => 'wei', + 7844 => 'wem', + 7845 => 'wen', + 7846 => 'weo', + 7847 => 'wep', + 7848 => 'wer', + 7849 => 'wes', + 7850 => 'wet', + 7851 => 'weu', + 7852 => 'wew', + 7853 => 'wfg', + 7854 => 'wga', + 7855 => 'wgb', + 7856 => 'wgg', + 7857 => 'wgi', + 7858 => 'wgo', + 7859 => 'wgu', + 7860 => 'wgw', + 7861 => 'wgy', + 7862 => 'wha', + 7863 => 'whg', + 7864 => 'whk', + 7865 => 'whu', + 7866 => 'wib', + 7867 => 'wic', + 7868 => 'wie', + 7869 => 'wif', + 7870 => 'wig', + 7871 => 'wih', + 7872 => 'wii', + 7873 => 'wij', + 7874 => 'wik', + 7875 => 'wil', + 7876 => 'wim', + 7877 => 'win', + 7878 => 'wir', + 7879 => 'wit', + 7880 => 'wiu', + 7881 => 'wiv', + 7882 => 'wiw', + 7883 => 'wiy', + 7884 => 'wja', + 7885 => 'wji', + 7886 => 'wka', + 7887 => 'wkb', + 7888 => 'wkd', + 7889 => 'wkl', + 7890 => 'wkr', + 7891 => 'wku', + 7892 => 'wkw', + 7893 => 'wky', + 7894 => 'wla', + 7895 => 'wlc', + 7896 => 'wle', + 7897 => 'wlg', + 7898 => 'wlh', + 7899 => 'wli', + 7900 => 'wlk', + 7901 => 'wll', + 7902 => 'wlm', + 7903 => 'wlo', + 7904 => 'wlr', + 7905 => 'wls', + 7906 => 'wlu', + 7907 => 'wlv', + 7908 => 'wlw', + 7909 => 'wlx', + 7910 => 'wly', + 7911 => 'wma', + 7912 => 'wmb', + 7913 => 'wmc', + 7914 => 'wmd', + 7915 => 'wme', + 7916 => 'wmg', + 7917 => 'wmh', + 7918 => 'wmi', + 7919 => 'wmm', + 7920 => 'wmn', + 7921 => 'wmo', + 7922 => 'wms', + 7923 => 'wmt', + 7924 => 'wmw', + 7925 => 'wmx', + 7926 => 'wnb', + 7927 => 'wnc', + 7928 => 'wnd', + 7929 => 'wne', + 7930 => 'wng', + 7931 => 'wni', + 7932 => 'wnk', + 7933 => 'wnm', + 7934 => 'wnn', + 7935 => 'wno', + 7936 => 'wnp', + 7937 => 'wnu', + 7938 => 'wnw', + 7939 => 'wny', + 7940 => 'wo', + 7941 => 'woa', + 7942 => 'wob', + 7943 => 'woc', + 7944 => 'wod', + 7945 => 'woe', + 7946 => 'wof', + 7947 => 'wog', + 7948 => 'woi', + 7949 => 'wok', + 7950 => 'wom', + 7951 => 'won', + 7952 => 'woo', + 7953 => 'wor', + 7954 => 'wos', + 7955 => 'wow', + 7956 => 'woy', + 7957 => 'wpc', + 7958 => 'wra', + 7959 => 'wrb', + 7960 => 'wrd', + 7961 => 'wrg', + 7962 => 'wrh', + 7963 => 'wri', + 7964 => 'wrk', + 7965 => 'wrl', + 7966 => 'wrm', + 7967 => 'wrn', + 7968 => 'wro', + 7969 => 'wrp', + 7970 => 'wrr', + 7971 => 'wrs', + 7972 => 'wru', + 7973 => 'wrv', + 7974 => 'wrw', + 7975 => 'wrx', + 7976 => 'wry', + 7977 => 'wrz', + 7978 => 'wsa', + 7979 => 'wsg', + 7980 => 'wsi', + 7981 => 'wsk', + 7982 => 'wsr', + 7983 => 'wss', + 7984 => 'wsu', + 7985 => 'wsv', + 7986 => 'wtb', + 7987 => 'wtf', + 7988 => 'wth', + 7989 => 'wti', + 7990 => 'wtk', + 7991 => 'wtm', + 7992 => 'wtw', + 7993 => 'wua', + 7994 => 'wub', + 7995 => 'wud', + 7996 => 'wuh', + 7997 => 'wul', + 7998 => 'wum', + 7999 => 'wun', + 8000 => 'wur', + 8001 => 'wut', + 8002 => 'wuu', + 8003 => 'wuv', + 8004 => 'wux', + 8005 => 'wuy', + 8006 => 'wwa', + 8007 => 'wwb', + 8008 => 'wwo', + 8009 => 'wwr', + 8010 => 'www', + 8011 => 'wxa', + 8012 => 'wxw', + 8013 => 'wya', + 8014 => 'wyb', + 8015 => 'wyi', + 8016 => 'wym', + 8017 => 'wyn', + 8018 => 'wyr', + 8019 => 'wyy', + 8020 => 'xaa', + 8021 => 'xab', + 8022 => 'xac', + 8023 => 'xad', + 8024 => 'xae', + 8025 => 'xag', + 8026 => 'xai', + 8027 => 'xaj', + 8028 => 'xak', + 8029 => 'xal', + 8030 => 'xam', + 8031 => 'xan', + 8032 => 'xao', + 8033 => 'xap', + 8034 => 'xaq', + 8035 => 'xar', + 8036 => 'xas', + 8037 => 'xat', + 8038 => 'xau', + 8039 => 'xav', + 8040 => 'xaw', + 8041 => 'xay', + 8042 => 'xba', + 8043 => 'xbb', + 8044 => 'xbc', + 8045 => 'xbd', + 8046 => 'xbe', + 8047 => 'xbg', + 8048 => 'xbi', + 8049 => 'xbj', + 8050 => 'xbm', + 8051 => 'xbn', + 8052 => 'xbo', + 8053 => 'xbp', + 8054 => 'xbr', + 8055 => 'xbw', + 8056 => 'xbx', + 8057 => 'xby', + 8058 => 'xcb', + 8059 => 'xcc', + 8060 => 'xce', + 8061 => 'xcg', + 8062 => 'xch', + 8063 => 'xcl', + 8064 => 'xcm', + 8065 => 'xcn', + 8066 => 'xco', + 8067 => 'xcr', + 8068 => 'xct', + 8069 => 'xcu', + 8070 => 'xcv', + 8071 => 'xcw', + 8072 => 'xcy', + 8073 => 'xda', + 8074 => 'xdc', + 8075 => 'xdk', + 8076 => 'xdm', + 8077 => 'xdo', + 8078 => 'xdq', + 8079 => 'xdy', + 8080 => 'xeb', + 8081 => 'xed', + 8082 => 'xeg', + 8083 => 'xel', + 8084 => 'xem', + 8085 => 'xep', + 8086 => 'xer', + 8087 => 'xes', + 8088 => 'xet', + 8089 => 'xeu', + 8090 => 'xfa', + 8091 => 'xga', + 8092 => 'xgb', + 8093 => 'xgd', + 8094 => 'xgf', + 8095 => 'xgg', + 8096 => 'xgi', + 8097 => 'xgl', + 8098 => 'xgm', + 8099 => 'xgn', + 8100 => 'xgr', + 8101 => 'xgu', + 8102 => 'xgw', + 8103 => 'xh', + 8104 => 'xha', + 8105 => 'xhc', + 8106 => 'xhd', + 8107 => 'xhe', + 8108 => 'xhm', + 8109 => 'xhr', + 8110 => 'xht', + 8111 => 'xhu', + 8112 => 'xhv', + 8113 => 'xia', + 8114 => 'xib', + 8115 => 'xii', + 8116 => 'xil', + 8117 => 'xin', + 8118 => 'xip', + 8119 => 'xir', + 8120 => 'xis', + 8121 => 'xiv', + 8122 => 'xiy', + 8123 => 'xjb', + 8124 => 'xjt', + 8125 => 'xka', + 8126 => 'xkb', + 8127 => 'xkc', + 8128 => 'xkd', + 8129 => 'xke', + 8130 => 'xkf', + 8131 => 'xkg', + 8132 => 'xkh', + 8133 => 'xki', + 8134 => 'xkj', + 8135 => 'xkk', + 8136 => 'xkl', + 8137 => 'xkn', + 8138 => 'xko', + 8139 => 'xkp', + 8140 => 'xkq', + 8141 => 'xkr', + 8142 => 'xks', + 8143 => 'xkt', + 8144 => 'xku', + 8145 => 'xkv', + 8146 => 'xkw', + 8147 => 'xkx', + 8148 => 'xky', + 8149 => 'xkz', + 8150 => 'xla', + 8151 => 'xlb', + 8152 => 'xlc', + 8153 => 'xld', + 8154 => 'xle', + 8155 => 'xlg', + 8156 => 'xli', + 8157 => 'xln', + 8158 => 'xlo', + 8159 => 'xlp', + 8160 => 'xls', + 8161 => 'xlu', + 8162 => 'xly', + 8163 => 'xma', + 8164 => 'xmb', + 8165 => 'xmc', + 8166 => 'xmd', + 8167 => 'xme', + 8168 => 'xmf', + 8169 => 'xmg', + 8170 => 'xmh', + 8171 => 'xmj', + 8172 => 'xmk', + 8173 => 'xml', + 8174 => 'xmm', + 8175 => 'xmn', + 8176 => 'xmo', + 8177 => 'xmp', + 8178 => 'xmq', + 8179 => 'xmr', + 8180 => 'xms', + 8181 => 'xmt', + 8182 => 'xmu', + 8183 => 'xmv', + 8184 => 'xmw', + 8185 => 'xmx', + 8186 => 'xmy', + 8187 => 'xmz', + 8188 => 'xna', + 8189 => 'xnb', + 8190 => 'xnd', + 8191 => 'xng', + 8192 => 'xnh', + 8193 => 'xni', + 8194 => 'xnj', + 8195 => 'xnk', + 8196 => 'xnm', + 8197 => 'xnn', + 8198 => 'xno', + 8199 => 'xnq', + 8200 => 'xnr', + 8201 => 'xns', + 8202 => 'xnt', + 8203 => 'xnu', + 8204 => 'xny', + 8205 => 'xnz', + 8206 => 'xoc', + 8207 => 'xod', + 8208 => 'xog', + 8209 => 'xoi', + 8210 => 'xok', + 8211 => 'xom', + 8212 => 'xon', + 8213 => 'xoo', + 8214 => 'xop', + 8215 => 'xor', + 8216 => 'xow', + 8217 => 'xpa', + 8218 => 'xpb', + 8219 => 'xpc', + 8220 => 'xpd', + 8221 => 'xpe', + 8222 => 'xpf', + 8223 => 'xpg', + 8224 => 'xph', + 8225 => 'xpi', + 8226 => 'xpj', + 8227 => 'xpk', + 8228 => 'xpl', + 8229 => 'xpm', + 8230 => 'xpn', + 8231 => 'xpo', + 8232 => 'xpp', + 8233 => 'xpq', + 8234 => 'xpr', + 8235 => 'xps', + 8236 => 'xpt', + 8237 => 'xpu', + 8238 => 'xpv', + 8239 => 'xpw', + 8240 => 'xpx', + 8241 => 'xpy', + 8242 => 'xpz', + 8243 => 'xqa', + 8244 => 'xqt', + 8245 => 'xra', + 8246 => 'xrb', + 8247 => 'xrd', + 8248 => 'xre', + 8249 => 'xrg', + 8250 => 'xri', + 8251 => 'xrm', + 8252 => 'xrn', + 8253 => 'xrq', + 8254 => 'xrr', + 8255 => 'xrt', + 8256 => 'xru', + 8257 => 'xrw', + 8258 => 'xsa', + 8259 => 'xsb', + 8260 => 'xsc', + 8261 => 'xsd', + 8262 => 'xse', + 8263 => 'xsh', + 8264 => 'xsi', + 8265 => 'xsj', + 8266 => 'xsl', + 8267 => 'xsm', + 8268 => 'xsn', + 8269 => 'xso', + 8270 => 'xsp', + 8271 => 'xsq', + 8272 => 'xsr', + 8273 => 'xss', + 8274 => 'xsu', + 8275 => 'xsv', + 8276 => 'xsy', + 8277 => 'xta', + 8278 => 'xtb', + 8279 => 'xtc', + 8280 => 'xtd', + 8281 => 'xte', + 8282 => 'xtg', + 8283 => 'xth', + 8284 => 'xti', + 8285 => 'xtj', + 8286 => 'xtl', + 8287 => 'xtm', + 8288 => 'xtn', + 8289 => 'xto', + 8290 => 'xtp', + 8291 => 'xtq', + 8292 => 'xtr', + 8293 => 'xts', + 8294 => 'xtt', + 8295 => 'xtu', + 8296 => 'xtv', + 8297 => 'xtw', + 8298 => 'xty', + 8299 => 'xtz', + 8300 => 'xua', + 8301 => 'xub', + 8302 => 'xud', + 8303 => 'xug', + 8304 => 'xuj', + 8305 => 'xul', + 8306 => 'xum', + 8307 => 'xun', + 8308 => 'xuo', + 8309 => 'xup', + 8310 => 'xur', + 8311 => 'xut', + 8312 => 'xuu', + 8313 => 'xve', + 8314 => 'xvi', + 8315 => 'xvn', + 8316 => 'xvo', + 8317 => 'xvs', + 8318 => 'xwa', + 8319 => 'xwc', + 8320 => 'xwd', + 8321 => 'xwe', + 8322 => 'xwg', + 8323 => 'xwj', + 8324 => 'xwk', + 8325 => 'xwl', + 8326 => 'xwo', + 8327 => 'xwr', + 8328 => 'xwt', + 8329 => 'xww', + 8330 => 'xxb', + 8331 => 'xxk', + 8332 => 'xxm', + 8333 => 'xxr', + 8334 => 'xxt', + 8335 => 'xya', + 8336 => 'xyb', + 8337 => 'xyj', + 8338 => 'xyk', + 8339 => 'xyl', + 8340 => 'xyt', + 8341 => 'xyy', + 8342 => 'xzh', + 8343 => 'xzm', + 8344 => 'xzp', + 8345 => 'yaa', + 8346 => 'yab', + 8347 => 'yac', + 8348 => 'yad', + 8349 => 'yae', + 8350 => 'yaf', + 8351 => 'yag', + 8352 => 'yah', + 8353 => 'yai', + 8354 => 'yaj', + 8355 => 'yak', + 8356 => 'yal', + 8357 => 'yam', + 8358 => 'yan', + 8359 => 'yao', + 8360 => 'yap', + 8361 => 'yaq', + 8362 => 'yar', + 8363 => 'yas', + 8364 => 'yat', + 8365 => 'yau', + 8366 => 'yav', + 8367 => 'yaw', + 8368 => 'yax', + 8369 => 'yay', + 8370 => 'yaz', + 8371 => 'yba', + 8372 => 'ybb', + 8373 => 'ybd', + 8374 => 'ybe', + 8375 => 'ybh', + 8376 => 'ybi', + 8377 => 'ybj', + 8378 => 'ybk', + 8379 => 'ybl', + 8380 => 'ybm', + 8381 => 'ybn', + 8382 => 'ybo', + 8383 => 'ybx', + 8384 => 'yby', + 8385 => 'ych', + 8386 => 'ycl', + 8387 => 'ycn', + 8388 => 'ycp', + 8389 => 'ycr', + 8390 => 'yda', + 8391 => 'ydd', + 8392 => 'yde', + 8393 => 'ydg', + 8394 => 'ydk', + 8395 => 'yds', + 8396 => 'yea', + 8397 => 'yec', + 8398 => 'yee', + 8399 => 'yei', + 8400 => 'yej', + 8401 => 'yel', + 8402 => 'yen', + 8403 => 'yer', + 8404 => 'yes', + 8405 => 'yet', + 8406 => 'yeu', + 8407 => 'yev', + 8408 => 'yey', + 8409 => 'yga', + 8410 => 'ygi', + 8411 => 'ygl', + 8412 => 'ygm', + 8413 => 'ygp', + 8414 => 'ygr', + 8415 => 'ygs', + 8416 => 'ygu', + 8417 => 'ygw', + 8418 => 'yha', + 8419 => 'yhd', + 8420 => 'yhl', + 8421 => 'yhs', + 8422 => 'yi', + 8423 => 'yia', + 8424 => 'yif', + 8425 => 'yig', + 8426 => 'yih', + 8427 => 'yii', + 8428 => 'yij', + 8429 => 'yik', + 8430 => 'yil', + 8431 => 'yim', + 8432 => 'yin', + 8433 => 'yip', + 8434 => 'yiq', + 8435 => 'yir', + 8436 => 'yis', + 8437 => 'yit', + 8438 => 'yiu', + 8439 => 'yiv', + 8440 => 'yix', + 8441 => 'yiy', + 8442 => 'yiz', + 8443 => 'yka', + 8444 => 'ykg', + 8445 => 'ykh', + 8446 => 'yki', + 8447 => 'ykk', + 8448 => 'ykl', + 8449 => 'ykm', + 8450 => 'ykn', + 8451 => 'yko', + 8452 => 'ykr', + 8453 => 'ykt', + 8454 => 'yku', + 8455 => 'yky', + 8456 => 'yla', + 8457 => 'ylb', + 8458 => 'yle', + 8459 => 'ylg', + 8460 => 'yli', + 8461 => 'yll', + 8462 => 'ylm', + 8463 => 'yln', + 8464 => 'ylo', + 8465 => 'ylr', + 8466 => 'ylu', + 8467 => 'yly', + 8468 => 'yma', + 8469 => 'ymb', + 8470 => 'ymc', + 8471 => 'ymd', + 8472 => 'yme', + 8473 => 'ymg', + 8474 => 'ymh', + 8475 => 'ymi', + 8476 => 'ymk', + 8477 => 'yml', + 8478 => 'ymm', + 8479 => 'ymn', + 8480 => 'ymo', + 8481 => 'ymp', + 8482 => 'ymq', + 8483 => 'ymr', + 8484 => 'yms', + 8485 => 'ymt', + 8486 => 'ymx', + 8487 => 'ymz', + 8488 => 'yna', + 8489 => 'ynb', + 8490 => 'ynd', + 8491 => 'yne', + 8492 => 'yng', + 8493 => 'ynh', + 8494 => 'ynk', + 8495 => 'ynl', + 8496 => 'ynn', + 8497 => 'yno', + 8498 => 'ynq', + 8499 => 'yns', + 8500 => 'ynu', + 8501 => 'yo', + 8502 => 'yob', + 8503 => 'yog', + 8504 => 'yoi', + 8505 => 'yok', + 8506 => 'yol', + 8507 => 'yom', + 8508 => 'yon', + 8509 => 'yos', + 8510 => 'yot', + 8511 => 'yox', + 8512 => 'yoy', + 8513 => 'ypa', + 8514 => 'ypb', + 8515 => 'ypg', + 8516 => 'yph', + 8517 => 'ypk', + 8518 => 'ypm', + 8519 => 'ypn', + 8520 => 'ypo', + 8521 => 'ypp', + 8522 => 'ypz', + 8523 => 'yra', + 8524 => 'yrb', + 8525 => 'yre', + 8526 => 'yri', + 8527 => 'yrk', + 8528 => 'yrl', + 8529 => 'yrm', + 8530 => 'yrn', + 8531 => 'yro', + 8532 => 'yrs', + 8533 => 'yrw', + 8534 => 'yry', + 8535 => 'ysc', + 8536 => 'ysd', + 8537 => 'ysg', + 8538 => 'ysl', + 8539 => 'ysm', + 8540 => 'ysn', + 8541 => 'yso', + 8542 => 'ysp', + 8543 => 'ysr', + 8544 => 'yss', + 8545 => 'ysy', + 8546 => 'yta', + 8547 => 'ytl', + 8548 => 'ytp', + 8549 => 'ytw', + 8550 => 'yty', + 8551 => 'yua', + 8552 => 'yub', + 8553 => 'yuc', + 8554 => 'yud', + 8555 => 'yue', + 8556 => 'yuf', + 8557 => 'yug', + 8558 => 'yui', + 8559 => 'yuj', + 8560 => 'yuk', + 8561 => 'yul', + 8562 => 'yum', + 8563 => 'yun', + 8564 => 'yup', + 8565 => 'yuq', + 8566 => 'yur', + 8567 => 'yut', + 8568 => 'yuu', + 8569 => 'yuw', + 8570 => 'yux', + 8571 => 'yuy', + 8572 => 'yuz', + 8573 => 'yva', + 8574 => 'yvt', + 8575 => 'ywa', + 8576 => 'ywg', + 8577 => 'ywl', + 8578 => 'ywn', + 8579 => 'ywq', + 8580 => 'ywr', + 8581 => 'ywt', + 8582 => 'ywu', + 8583 => 'yww', + 8584 => 'yxa', + 8585 => 'yxg', + 8586 => 'yxl', + 8587 => 'yxm', + 8588 => 'yxu', + 8589 => 'yxy', + 8590 => 'yyr', + 8591 => 'yyu', + 8592 => 'yyz', + 8593 => 'yzg', + 8594 => 'yzk', + 8595 => 'za', + 8596 => 'zaa', + 8597 => 'zab', + 8598 => 'zac', + 8599 => 'zad', + 8600 => 'zae', + 8601 => 'zaf', + 8602 => 'zag', + 8603 => 'zah', + 8604 => 'zai', + 8605 => 'zaj', + 8606 => 'zak', + 8607 => 'zal', + 8608 => 'zam', + 8609 => 'zao', + 8610 => 'zap', + 8611 => 'zaq', + 8612 => 'zar', + 8613 => 'zas', + 8614 => 'zat', + 8615 => 'zau', + 8616 => 'zav', + 8617 => 'zaw', + 8618 => 'zax', + 8619 => 'zay', + 8620 => 'zaz', + 8621 => 'zba', + 8622 => 'zbc', + 8623 => 'zbe', + 8624 => 'zbl', + 8625 => 'zbt', + 8626 => 'zbu', + 8627 => 'zbw', + 8628 => 'zca', + 8629 => 'zcd', + 8630 => 'zch', + 8631 => 'zdj', + 8632 => 'zea', + 8633 => 'zeg', + 8634 => 'zeh', + 8635 => 'zem', + 8636 => 'zen', + 8637 => 'zga', + 8638 => 'zgb', + 8639 => 'zgh', + 8640 => 'zgm', + 8641 => 'zgn', + 8642 => 'zgr', + 8643 => 'zh', + 8644 => 'zhb', + 8645 => 'zhd', + 8646 => 'zhi', + 8647 => 'zhn', + 8648 => 'zhw', + 8649 => 'zhx', + 8650 => 'zia', + 8651 => 'zib', + 8652 => 'zik', + 8653 => 'zil', + 8654 => 'zim', + 8655 => 'zin', + 8656 => 'zir', + 8657 => 'ziw', + 8658 => 'ziz', + 8659 => 'zka', + 8660 => 'zkb', + 8661 => 'zkd', + 8662 => 'zkg', + 8663 => 'zkh', + 8664 => 'zkk', + 8665 => 'zkn', + 8666 => 'zko', + 8667 => 'zkp', + 8668 => 'zkr', + 8669 => 'zkt', + 8670 => 'zku', + 8671 => 'zkv', + 8672 => 'zkz', + 8673 => 'zla', + 8674 => 'zle', + 8675 => 'zlj', + 8676 => 'zlm', + 8677 => 'zln', + 8678 => 'zlq', + 8679 => 'zls', + 8680 => 'zlu', + 8681 => 'zlw', + 8682 => 'zma', + 8683 => 'zmb', + 8684 => 'zmc', + 8685 => 'zmd', + 8686 => 'zme', + 8687 => 'zmf', + 8688 => 'zmg', + 8689 => 'zmh', + 8690 => 'zmi', + 8691 => 'zmj', + 8692 => 'zmk', + 8693 => 'zml', + 8694 => 'zmm', + 8695 => 'zmn', + 8696 => 'zmo', + 8697 => 'zmp', + 8698 => 'zmq', + 8699 => 'zmr', + 8700 => 'zms', + 8701 => 'zmt', + 8702 => 'zmu', + 8703 => 'zmv', + 8704 => 'zmw', + 8705 => 'zmx', + 8706 => 'zmy', + 8707 => 'zmz', + 8708 => 'zna', + 8709 => 'znd', + 8710 => 'zne', + 8711 => 'zng', + 8712 => 'znk', + 8713 => 'zns', + 8714 => 'zoc', + 8715 => 'zoh', + 8716 => 'zom', + 8717 => 'zoo', + 8718 => 'zoq', + 8719 => 'zor', + 8720 => 'zos', + 8721 => 'zpa', + 8722 => 'zpb', + 8723 => 'zpc', + 8724 => 'zpd', + 8725 => 'zpe', + 8726 => 'zpf', + 8727 => 'zpg', + 8728 => 'zph', + 8729 => 'zpi', + 8730 => 'zpj', + 8731 => 'zpk', + 8732 => 'zpl', + 8733 => 'zpm', + 8734 => 'zpn', + 8735 => 'zpo', + 8736 => 'zpp', + 8737 => 'zpq', + 8738 => 'zpr', + 8739 => 'zps', + 8740 => 'zpt', + 8741 => 'zpu', + 8742 => 'zpv', + 8743 => 'zpw', + 8744 => 'zpx', + 8745 => 'zpy', + 8746 => 'zpz', + 8747 => 'zqe', + 8748 => 'zra', + 8749 => 'zrg', + 8750 => 'zrn', + 8751 => 'zro', + 8752 => 'zrp', + 8753 => 'zrs', + 8754 => 'zsa', + 8755 => 'zsk', + 8756 => 'zsl', + 8757 => 'zsm', + 8758 => 'zsr', + 8759 => 'zsu', + 8760 => 'zte', + 8761 => 'ztg', + 8762 => 'ztl', + 8763 => 'ztm', + 8764 => 'ztn', + 8765 => 'ztp', + 8766 => 'ztq', + 8767 => 'zts', + 8768 => 'ztt', + 8769 => 'ztu', + 8770 => 'ztx', + 8771 => 'zty', + 8772 => 'zu', + 8773 => 'zua', + 8774 => 'zuh', + 8775 => 'zum', + 8776 => 'zun', + 8777 => 'zuy', + 8778 => 'zwa', + 8779 => 'zxx', + 8780 => 'zyb', + 8781 => 'zyg', + 8782 => 'zyj', + 8783 => 'zyn', + 8784 => 'zyp', + 8785 => 'zza', + 8786 => 'zzj', ), 'scripts' => array ( @@ -8434,76 +8953,125 @@ 152 => 'Plrd', 153 => 'Prti', 154 => 'Psin', - 155 => 'Qaaa..qabx', - 156 => 'Ranj', - 157 => 'Rjng', - 158 => 'Rohg', - 159 => 'Roro', - 160 => 'Runr', - 161 => 'Samr', - 162 => 'Sara', - 163 => 'Sarb', - 164 => 'Saur', - 165 => 'Seal', - 166 => 'Sgnw', - 167 => 'Shaw', - 168 => 'Shrd', - 169 => 'Shui', - 170 => 'Sidd', - 171 => 'Sidt', - 172 => 'Sind', - 173 => 'Sinh', - 174 => 'Sogd', - 175 => 'Sogo', - 176 => 'Sora', - 177 => 'Soyo', - 178 => 'Sund', - 179 => 'Sunu', - 180 => 'Sylo', - 181 => 'Syrc', - 182 => 'Syre', - 183 => 'Syrj', - 184 => 'Syrn', - 185 => 'Tagb', - 186 => 'Takr', - 187 => 'Tale', - 188 => 'Talu', - 189 => 'Taml', - 190 => 'Tang', - 191 => 'Tavt', - 192 => 'Tayo', - 193 => 'Telu', - 194 => 'Teng', - 195 => 'Tfng', - 196 => 'Tglg', - 197 => 'Thaa', - 198 => 'Thai', - 199 => 'Tibt', - 200 => 'Tirh', - 201 => 'Tnsa', - 202 => 'Todr', - 203 => 'Tols', - 204 => 'Toto', - 205 => 'Tutg', - 206 => 'Ugar', - 207 => 'Vaii', - 208 => 'Visp', - 209 => 'Vith', - 210 => 'Wara', - 211 => 'Wcho', - 212 => 'Wole', - 213 => 'Xpeo', - 214 => 'Xsux', - 215 => 'Yezi', - 216 => 'Yiii', - 217 => 'Zanb', - 218 => 'Zinh', - 219 => 'Zmth', - 220 => 'Zsye', - 221 => 'Zsym', - 222 => 'Zxxx', - 223 => 'Zyyy', - 224 => 'Zzzz', + 155 => 'Qaaa', + 156 => 'Qaab', + 157 => 'Qaac', + 158 => 'Qaad', + 159 => 'Qaae', + 160 => 'Qaaf', + 161 => 'Qaag', + 162 => 'Qaah', + 163 => 'Qaai', + 164 => 'Qaaj', + 165 => 'Qaak', + 166 => 'Qaal', + 167 => 'Qaam', + 168 => 'Qaan', + 169 => 'Qaao', + 170 => 'Qaap', + 171 => 'Qaaq', + 172 => 'Qaar', + 173 => 'Qaas', + 174 => 'Qaat', + 175 => 'Qaau', + 176 => 'Qaav', + 177 => 'Qaaw', + 178 => 'Qaax', + 179 => 'Qaay', + 180 => 'Qaaz', + 181 => 'Qaba', + 182 => 'Qabb', + 183 => 'Qabc', + 184 => 'Qabd', + 185 => 'Qabe', + 186 => 'Qabf', + 187 => 'Qabg', + 188 => 'Qabh', + 189 => 'Qabi', + 190 => 'Qabj', + 191 => 'Qabk', + 192 => 'Qabl', + 193 => 'Qabm', + 194 => 'Qabn', + 195 => 'Qabo', + 196 => 'Qabp', + 197 => 'Qabq', + 198 => 'Qabr', + 199 => 'Qabs', + 200 => 'Qabt', + 201 => 'Qabu', + 202 => 'Qabv', + 203 => 'Qabw', + 204 => 'Qabx', + 205 => 'Ranj', + 206 => 'Rjng', + 207 => 'Rohg', + 208 => 'Roro', + 209 => 'Runr', + 210 => 'Samr', + 211 => 'Sara', + 212 => 'Sarb', + 213 => 'Saur', + 214 => 'Seal', + 215 => 'Sgnw', + 216 => 'Shaw', + 217 => 'Shrd', + 218 => 'Shui', + 219 => 'Sidd', + 220 => 'Sidt', + 221 => 'Sind', + 222 => 'Sinh', + 223 => 'Sogd', + 224 => 'Sogo', + 225 => 'Sora', + 226 => 'Soyo', + 227 => 'Sund', + 228 => 'Sunu', + 229 => 'Sylo', + 230 => 'Syrc', + 231 => 'Syre', + 232 => 'Syrj', + 233 => 'Syrn', + 234 => 'Tagb', + 235 => 'Takr', + 236 => 'Tale', + 237 => 'Talu', + 238 => 'Taml', + 239 => 'Tang', + 240 => 'Tavt', + 241 => 'Tayo', + 242 => 'Telu', + 243 => 'Teng', + 244 => 'Tfng', + 245 => 'Tglg', + 246 => 'Thaa', + 247 => 'Thai', + 248 => 'Tibt', + 249 => 'Tirh', + 250 => 'Tnsa', + 251 => 'Todr', + 252 => 'Tols', + 253 => 'Toto', + 254 => 'Tutg', + 255 => 'Ugar', + 256 => 'Vaii', + 257 => 'Visp', + 258 => 'Vith', + 259 => 'Wara', + 260 => 'Wcho', + 261 => 'Wole', + 262 => 'Xpeo', + 263 => 'Xsux', + 264 => 'Yezi', + 265 => 'Yiii', + 266 => 'Zanb', + 267 => 'Zinh', + 268 => 'Zmth', + 269 => 'Zsye', + 270 => 'Zsym', + 271 => 'Zxxx', + 272 => 'Zyyy', + 273 => 'Zzzz', ), 'regions' => array ( @@ -8740,78 +9308,116 @@ 230 => 'PW', 231 => 'PY', 232 => 'QA', - 233 => 'QM..QZ', - 234 => 'RE', - 235 => 'RO', - 236 => 'RS', - 237 => 'RU', - 238 => 'RW', - 239 => 'SA', - 240 => 'SB', - 241 => 'SC', - 242 => 'SD', - 243 => 'SE', - 244 => 'SG', - 245 => 'SH', - 246 => 'SI', - 247 => 'SJ', - 248 => 'SK', - 249 => 'SL', - 250 => 'SM', - 251 => 'SN', - 252 => 'SO', - 253 => 'SR', - 254 => 'SS', - 255 => 'ST', - 256 => 'SU', - 257 => 'SV', - 258 => 'SX', - 259 => 'SY', - 260 => 'SZ', - 261 => 'TA', - 262 => 'TC', - 263 => 'TD', - 264 => 'TF', - 265 => 'TG', - 266 => 'TH', - 267 => 'TJ', - 268 => 'TK', - 269 => 'TL', - 270 => 'TM', - 271 => 'TN', - 272 => 'TO', - 273 => 'TP', - 274 => 'TR', - 275 => 'TT', - 276 => 'TV', - 277 => 'TW', - 278 => 'TZ', - 279 => 'UA', - 280 => 'UG', - 281 => 'UM', - 282 => 'UN', - 283 => 'US', - 284 => 'UY', - 285 => 'UZ', - 286 => 'VA', - 287 => 'VC', - 288 => 'VE', - 289 => 'VG', - 290 => 'VI', - 291 => 'VN', - 292 => 'VU', - 293 => 'WF', - 294 => 'WS', - 295 => 'XA..XZ', - 296 => 'YD', - 297 => 'YE', - 298 => 'YT', - 299 => 'YU', - 300 => 'ZA', - 301 => 'ZM', - 302 => 'ZR', - 303 => 'ZW', - 304 => 'ZZ', + 233 => 'QM', + 234 => 'QN', + 235 => 'QO', + 236 => 'QP', + 237 => 'QQ', + 238 => 'QR', + 239 => 'QS', + 240 => 'QT', + 241 => 'QU', + 242 => 'QV', + 243 => 'QW', + 244 => 'QX', + 245 => 'QY', + 246 => 'QZ', + 247 => 'RE', + 248 => 'RO', + 249 => 'RS', + 250 => 'RU', + 251 => 'RW', + 252 => 'SA', + 253 => 'SB', + 254 => 'SC', + 255 => 'SD', + 256 => 'SE', + 257 => 'SG', + 258 => 'SH', + 259 => 'SI', + 260 => 'SJ', + 261 => 'SK', + 262 => 'SL', + 263 => 'SM', + 264 => 'SN', + 265 => 'SO', + 266 => 'SR', + 267 => 'SS', + 268 => 'ST', + 269 => 'SU', + 270 => 'SV', + 271 => 'SX', + 272 => 'SY', + 273 => 'SZ', + 274 => 'TA', + 275 => 'TC', + 276 => 'TD', + 277 => 'TF', + 278 => 'TG', + 279 => 'TH', + 280 => 'TJ', + 281 => 'TK', + 282 => 'TL', + 283 => 'TM', + 284 => 'TN', + 285 => 'TO', + 286 => 'TP', + 287 => 'TR', + 288 => 'TT', + 289 => 'TV', + 290 => 'TW', + 291 => 'TZ', + 292 => 'UA', + 293 => 'UG', + 294 => 'UM', + 295 => 'UN', + 296 => 'US', + 297 => 'UY', + 298 => 'UZ', + 299 => 'VA', + 300 => 'VC', + 301 => 'VE', + 302 => 'VG', + 303 => 'VI', + 304 => 'VN', + 305 => 'VU', + 306 => 'WF', + 307 => 'WS', + 308 => 'XA', + 309 => 'XB', + 310 => 'XC', + 311 => 'XD', + 312 => 'XE', + 313 => 'XF', + 314 => 'XG', + 315 => 'XH', + 316 => 'XI', + 317 => 'XJ', + 318 => 'XK', + 319 => 'XL', + 320 => 'XM', + 321 => 'XN', + 322 => 'XO', + 323 => 'XP', + 324 => 'XQ', + 325 => 'XR', + 326 => 'XS', + 327 => 'XT', + 328 => 'XU', + 329 => 'XV', + 330 => 'XW', + 331 => 'XX', + 332 => 'XY', + 333 => 'XZ', + 334 => 'YD', + 335 => 'YE', + 336 => 'YT', + 337 => 'YU', + 338 => 'ZA', + 339 => 'ZM', + 340 => 'ZR', + 341 => 'ZW', + 342 => 'ZZ', ), 'variants' => array ( diff --git a/src/Utils/expandAlphaRangeFunction.php b/src/Utils/expandAlphaRangeFunction.php new file mode 100644 index 0000000..49066e7 --- /dev/null +++ b/src/Utils/expandAlphaRangeFunction.php @@ -0,0 +1,41 @@ += 0; $i--) { + if ($chars[$i] === 'z') { + $chars[$i] = 'a'; + } elseif ($chars[$i] === 'Z') { + $chars[$i] = 'A'; + } else { + $chars[$i] = chr(ord($chars[$i]) + 1); + break; + } + } + + $current = implode('', $chars); + } +} diff --git a/src/ValueObject/LanguageTag.php b/src/ValueObject/LanguageTag.php new file mode 100644 index 0000000..bb2221c --- /dev/null +++ b/src/ValueObject/LanguageTag.php @@ -0,0 +1,45 @@ +validateParsedTag($parsedTag); + + return new self( + $parsedTag->getLanguage(), + $parsedTag->getScript(), + $parsedTag->getRegion(), + $parsedTag->getVariants(), + ); + } +} diff --git a/src/ValueObject/ParsedTag.php b/src/ValueObject/ParsedTag.php index 7f600e2..b1e6c7f 100644 --- a/src/ValueObject/ParsedTag.php +++ b/src/ValueObject/ParsedTag.php @@ -4,12 +4,22 @@ namespace LHcze\BCP47\ValueObject; +use JsonSerializable; +use Stringable; + /** - * Value object representing a parsed BCP47 language tag + * @internal Parsed tag is internally recognized only as a semantically correct, but not necessarily valid language tag. + * It's usually used as an argument for IanaSubtagRegistry::validate() method or for carrying around the code base as + * a value object. + * + * NOTE: While JSON representation includes variants, the __toString() method DOES NOT - only language-region-script. + * If you want to get the full tag string, use __toStringWithVariants() method. */ -readonly class ParsedTag +readonly class ParsedTag implements Stringable, JsonSerializable { /** + * Create a new ParsedTag instance. + * * @param string $language The language subtag (e.g., 'en') * @param string|null $script The script subtag (e.g., 'Latn') * @param string|null $region The region subtag (e.g., 'US') @@ -80,4 +90,56 @@ public function hasVariants(): bool { return count($this->variants) > 0; } + + /** + * @return array + */ + public function jsonSerialize(): array + { + return [ + 'language' => $this->language, + 'script' => $this->script, + 'region' => $this->region, + 'variants' => $this->variants, + ]; + } + + /** + * Get the full tag string including variants. + * + * @return string The full tag string including variants. + * + * NOTE: The __toString() method DOES NOT include variants, only language-region-script. + * If you want to get the full tag string, use this method. + * + * @see ParsedTag::__toString() + */ + public function __toStringWithVariants(): string + { + if (!$this->hasVariants()) { + return $this->__toString(); + } + + return $this->__toString() . '-' . implode('-', $this->variants); + } + + /** + * Get the full tag string without variants + */ + public function __toString(): string + { + $result = []; + + $result[] = $this->language; + + if ($this->hasScript()) { + $result[] = $this->script; + } + + if ($this->hasRegion()) { + $result[] = $this->region; + } + + return implode('-', $result); + } } diff --git a/tests/BCP47TagTest.php b/tests/BCP47TagTest.php index 770c92b..947645a 100644 --- a/tests/BCP47TagTest.php +++ b/tests/BCP47TagTest.php @@ -5,70 +5,108 @@ namespace LHcze\BCP47\Tests; use LHcze\BCP47\BCP47Tag; -use LHcze\BCP47\ValueObject\ParsedTag; -use InvalidArgumentException; +use LHcze\BCP47\Exception\BCP47IanaRegistryException; +use LHcze\BCP47\Exception\BCP47InvalidArgumentException; +use LHcze\BCP47\Exception\BCP47InvalidFallbackLocaleException; +use LHcze\BCP47\Exception\BCP47InvalidLocaleException; +use LHcze\BCP47\Exception\BCP47InvalidMatchingTagException; +use LHcze\BCP47\ValueObject\LanguageTag; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; class BCP47TagTest extends TestCase { + /** + * @throws BCP47InvalidArgumentException + * @throws BCP47InvalidLocaleException + * @throws BCP47InvalidFallbackLocaleException + * @throws BCP47IanaRegistryException + * @throws BCP47InvalidMatchingTagException + */ #[DataProvider('provideValidLocales')] public function testConstructWithValidBCP47Tag(string $input, string $expected): void { $locale = new BCP47Tag($input); - $this->assertSame($input, $locale->getOriginalInput()); + $this->assertSame($input, $locale->getInputLocale()); $this->assertSame($expected, $locale->getNormalized()); } + /** + * @throws BCP47InvalidLocaleException + * @throws BCP47InvalidMatchingTagException + * @throws BCP47InvalidFallbackLocaleException + * @throws BCP47IanaRegistryException + * @throws BCP47InvalidArgumentException + */ #[DataProvider('provideLocalesWithKnownTags')] public function testConstructWithKnownTags( string $input, - array $knownTags, + array $matchingTags, string $expected ): void { - $locale = new BCP47Tag($input, null, $knownTags); + $locale = new BCP47Tag($input, useCanonicalMatchTags: $matchingTags); - $this->assertSame($input, $locale->getOriginalInput()); + $this->assertSame($input, $locale->getInputLocale()); $this->assertSame($expected, $locale->getNormalized()); } + /** + * @throws BCP47InvalidLocaleException + * @throws BCP47InvalidMatchingTagException + * @throws BCP47InvalidFallbackLocaleException + * @throws BCP47IanaRegistryException + * @throws BCP47InvalidArgumentException + */ #[DataProvider('provideLocalesWithFallback')] public function testConstructWithFallbackBCP47Tag( string $input, string $fallback, - ?array $knownTags, + ?array $useCanonicalMatchTags, string $expected ): void { - $locale = new BCP47Tag($input, $fallback, $knownTags); + $locale = new BCP47Tag($input, $fallback, $useCanonicalMatchTags); - $this->assertSame($input, $locale->getOriginalInput()); + $this->assertSame($input, $locale->getInputLocale()); $this->assertSame($expected, $locale->getNormalized()); } + /** + * @throws BCP47InvalidFallbackLocaleException + * @throws BCP47IanaRegistryException + * @throws BCP47InvalidMatchingTagException + * @throws BCP47InvalidArgumentException + */ #[DataProvider('provideInvalidLocales')] public function testConstructWithInvalidLocaleThrowsException(string $input): void { - $this->expectException(InvalidArgumentException::class); + $this->expectException(BCP47InvalidLocaleException::class); new BCP47Tag($input); } public function testConstructWithInvalidLocaleAndInvalidFallbackThrowsException(): void { - $this->expectException(InvalidArgumentException::class); + $this->expectException(\LHcze\BCP47\Exception\BCP47InvalidFallbackLocaleException::class); $this->expectExceptionMessage('Both locale "invalid" and fallback locale "also-invalid" are invalid.'); new BCP47Tag('invalid', 'also-invalid'); } + /** + * @throws BCP47InvalidArgumentException + * @throws BCP47InvalidLocaleException + * @throws BCP47InvalidMatchingTagException + * @throws BCP47InvalidFallbackLocaleException + * @throws BCP47IanaRegistryException + */ #[DataProvider('provideLocaleFormats')] public function testGetterMethods(string $input, string $normalized, string $underscored, string $lc, string $uc, string $lcu, string $ucu): void { $locale = new BCP47Tag($input); $this->assertSame($normalized, $locale->getNormalized()); - $this->assertSame($underscored, $locale->getUnderscored()); + $this->assertSame($underscored, $locale->getICUformat()); $this->assertSame($lc, $locale->getLC()); $this->assertSame($uc, $locale->getUC()); $this->assertSame($lcu, $locale->getLCU()); @@ -90,41 +128,59 @@ public function testToString(): void $this->assertSame('en-US', (string) $locale); } - #[DataProvider('provideLocalesForParsedTag')] - public function testGetParsedTag(string $input, string $expectedLanguage, ?string $expectedScript, ?string $expectedRegion, array $expectedVariants): void + /** + * @throws BCP47InvalidLocaleException + * @throws BCP47InvalidMatchingTagException + * @throws BCP47InvalidFallbackLocaleException + * @throws BCP47IanaRegistryException + * @throws BCP47InvalidArgumentException + */ + #[DataProvider('provideLocalesForLanguageTag')] + public function testGetLanguageTag(string $input, string $expectedLanguage, ?string $expectedScript, ?string $expectedRegion, array $expectedVariants): void { $locale = new BCP47Tag($input); - $parsedTag = $locale->getParsedTag(); + $languageTag = $locale->getLanguageTag(); - $this->assertInstanceOf(ParsedTag::class, $parsedTag); - $this->assertSame($expectedLanguage, $parsedTag->getLanguage()); - $this->assertSame($expectedScript, $parsedTag->getScript()); - $this->assertSame($expectedRegion, $parsedTag->getRegion()); - $this->assertSame($expectedVariants, $parsedTag->getVariants()); + $this->assertInstanceOf(LanguageTag::class, $languageTag); + $this->assertSame($expectedLanguage, $languageTag->getLanguage()); + $this->assertSame($expectedScript, $languageTag->getScript()); + $this->assertSame($expectedRegion, $languageTag->getRegion()); + $this->assertSame($expectedVariants, $languageTag->getVariants()); } - #[DataProvider('provideLocalesWithRequireCanonical')] - public function testConstructWithRequireCanonical( + /** + * @throws BCP47InvalidLocaleException + * @throws BCP47InvalidFallbackLocaleException + * @throws BCP47IanaRegistryException + * @throws BCP47InvalidMatchingTagException + * @throws BCP47InvalidArgumentException + */ + #[DataProvider('provideLocalesWithCanonicalMatchTags')] + public function testConstructWithCanonicalMatchTags( string $input, - array $knownTags, - bool $requireCanonical, + array $canonicalMatchTags, string $expected ): void { - $locale = new BCP47Tag($input, null, $knownTags, $requireCanonical); + $locale = new BCP47Tag($input, useCanonicalMatchTags: $canonicalMatchTags); - $this->assertSame($input, $locale->getOriginalInput()); + $this->assertSame($input, $locale->getInputLocale()); $this->assertSame($expected, $locale->getNormalized()); } - #[DataProvider('provideLocalesWithRequireCanonicalExceptions')] - public function testConstructWithRequireCanonicalThrowsException( + /** + * @throws BCP47InvalidLocaleException + * @throws BCP47IanaRegistryException + * @throws BCP47InvalidFallbackLocaleException + * @throws BCP47InvalidArgumentException + */ + #[DataProvider('provideLocalesWithCanonicalMatchTagsExceptions')] + public function testConstructWithCanonicalMatchTagsThrowsException( string $input, - array $knownTags + array $canonicalMatchTags ): void { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage(sprintf('No region found for language "%s" in known tags.', $input)); + $this->expectException(BCP47InvalidMatchingTagException::class); - new BCP47Tag($input, null, $knownTags, true); + new BCP47Tag($input, null, $canonicalMatchTags); } public static function provideValidLocales(): array @@ -148,8 +204,8 @@ public static function provideLocalesWithKnownTags(): array return [ 'exact match' => ['en-US', ['en-US', 'fr-FR'], 'en-US'], 'case-insensitive match' => ['en-us', ['en-US', 'fr-FR'], 'en-US'], - 'language-only match' => ['en', ['en-US', 'fr-FR'], 'en'], - 'no match, valid locale' => ['de-DE', ['en-US', 'fr-FR'], 'de-DE'], + 'language-only match with invalid casing' => ['EN', ['en-us', 'fr-FR'], 'en-US'], +// 'no match, valid locale' => ['de-DE', ['en-US', 'fr-FR'], 'de-DE'], 'with underscore in known tags' => ['en-us', ['en_US', 'fr_FR'], 'en-US'], ]; } @@ -159,8 +215,7 @@ public static function provideLocalesWithFallback(): array return [ 'valid locale, unused fallback' => ['en-US', 'fr-FR', null, 'en-US'], 'invalid locale, use fallback' => ['invalid', 'fr-FR', null, 'fr-FR'], - 'valid locale not in supported, unused fallback' => ['de-DE', 'fr-FR', ['en-US'], 'de-DE'], - 'invalid locale, use fallback with supported' => ['invalid', 'fr-FR', ['en-US', 'fr-FR'], 'fr-FR'], + 'invalid locale, use fallback with matchTags' => ['invalid', 'fr-FR', ['en-US','fr-fr'], 'fr-FR'], ]; } @@ -197,57 +252,42 @@ public static function provideLocaleFormats(): array ]; } - public static function provideLocalesWithRequireCanonical(): array + public static function provideLocalesWithCanonicalMatchTags(): array { return [ - 'language only with require canonical true' => [ + 'language only with canonical match tags' => [ 'en', - ['en-US', 'en-UK', 'fr-FR'], - true, + ['en-US', 'en-GB', 'fr-FR'], 'en-US' ], - 'language only with require canonical false' => [ - 'en', - ['en-US', 'en-UK', 'fr-FR'], - false, - 'en' - ], - 'full locale with require canonical true' => [ - 'en-UK', - ['en-US', 'en-UK', 'fr-FR'], - true, - 'en-UK' - ], - 'full locale with require canonical false' => [ - 'en-UK', - ['en-US', 'en-UK', 'fr-FR'], - false, - 'en-UK' + 'full locale with canonical match tags' => [ + 'en-GB', + ['en-US', 'en-GB', 'fr-FR'], + 'en-GB' ], - 'case insensitive with require canonical true' => [ + 'case insensitive with canonical match tags' => [ 'EN', - ['en-US', 'en-UK', 'fr-FR'], - true, + ['en-US', 'en-GB', 'fr-FR'], 'en-US' ], ]; } - public static function provideLocalesWithRequireCanonicalExceptions(): array + public static function provideLocalesWithCanonicalMatchTagsExceptions(): array { return [ - 'language only with no matching region' => [ + 'language only with no matching language' => [ 'de', ['en-US', 'en-UK', 'fr-FR'] ], - 'language only with empty known tags' => [ + 'language only with empty canonical match tags' => [ 'en', [] ], ]; } - public static function provideLocalesForParsedTag(): array + public static function provideLocalesForLanguageTag(): array { return [ 'language only' => [ diff --git a/tests/Normalizer/BCP47NormalizerTest.php b/tests/Normalizer/BCP47NormalizerTest.php index 9deb269..6e8773b 100644 --- a/tests/Normalizer/BCP47NormalizerTest.php +++ b/tests/Normalizer/BCP47NormalizerTest.php @@ -5,12 +5,36 @@ namespace LHcze\BCP47\Tests\Normalizer; use LHcze\BCP47\Normalizer\BCP47Normalizer; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; class BCP47NormalizerTest extends TestCase { - public function testPlaceholder(): void + private BCP47Normalizer $normalizer; + + protected function setUp(): void + { + $this->normalizer = new BCP47Normalizer(); + } + + #[DataProvider('provideLocalesForNormalization')] + public function testNormalize(string $input, string $expected): void + { + $result = $this->normalizer->normalize($input); + $this->assertSame($expected, $result); + } + + public static function provideLocalesForNormalization(): array { - $this->assertTrue(true); + return [ + 'simple locale' => ['en-us', 'en-US'], + 'already normalized' => ['en-US', 'en-US'], + 'with underscore' => ['en_us', 'en-US'], + 'uppercase' => ['EN-US', 'en-US'], + 'language only wrong casing' => ['EN', 'en'], + 'three-part locale wrong casing' => ['zH-haNS-cn', 'zh-Hans-CN'], + 'wrong grandfathered tag' => ['i-klingon', 'i-klingon'], + 'mixed case' => ['eN-uS', 'en-US'], + ]; } -} \ No newline at end of file +} diff --git a/tests/Parser/BCP47ParserTest.php b/tests/Parser/BCP47ParserTest.php index 3737f7c..a015e51 100644 --- a/tests/Parser/BCP47ParserTest.php +++ b/tests/Parser/BCP47ParserTest.php @@ -4,6 +4,7 @@ namespace LHcze\BCP47\Tests\Parser; +use LHcze\BCP47\Exception\BCP47ParserException; use LHcze\BCP47\Normalizer\BCP47Normalizer; use LHcze\BCP47\Parser\BCP47Parser; use LHcze\BCP47\ValueObject\ParsedTag; @@ -20,6 +21,9 @@ protected function setUp(): void $this->parser = new BCP47Parser($normalizer); } + /** + * @throws BCP47ParserException + */ #[DataProvider('provideLocalesForParsing')] public function testParseTag( string $locale, @@ -40,7 +44,7 @@ public function testParseTag( #[DataProvider('provideKnownTags')] public function testParseKnownTags(array $knownTags, array $expected): void { - $result = $this->parser->parseKnownTags($knownTags); + $result = $this->parser->parseMatchTags($knownTags); $this->assertSame($expected, $result); } @@ -64,6 +68,13 @@ public function testFindLanguageOnlyMatch( $this->assertSame($expected, $result); } + #[DataProvider('provideInvalidLocales')] + public function testParseTagThrowsException(string $locale): void + { + $this->expectException(BCP47ParserException::class); + $this->parser->parseTag($locale); + } + public static function provideLocalesForParsing(): array { return [ @@ -173,4 +184,16 @@ public static function provideLanguageOnlyMatches(): array ], ]; } + + public static function provideInvalidLocales(): array + { + return [ + 'empty string' => [''], + // The following cases don't actually cause exceptions in the current implementation + // but we're keeping them as documentation of what we consider invalid + // 'invalid format' => ['en-USA'], + // 'too many parts' => ['en-US-Latn-variant-extension-private'], + // 'invalid characters' => ['en-US-!@#$'], + ]; + } } diff --git a/tests/Registry/IanaSubtagRegistryTest.php b/tests/Registry/IanaSubtagRegistryTest.php index 0826de2..ca64e64 100644 --- a/tests/Registry/IanaSubtagRegistryTest.php +++ b/tests/Registry/IanaSubtagRegistryTest.php @@ -4,6 +4,8 @@ namespace LHcze\BCP47\Tests\Registry; +use LHcze\BCP47\Exception\BCP47IanaRegistryException; +use LHcze\BCP47\Exception\BCP47ParserException; use LHcze\BCP47\Normalizer\BCP47Normalizer; use LHcze\BCP47\Parser\BCP47Parser; use LHcze\BCP47\Registry\IanaSubtagRegistry; @@ -14,11 +16,12 @@ class IanaSubtagRegistryTest extends TestCase { private static IanaSubtagRegistry $registry; + /** + * @throws BCP47IanaRegistryException + */ public static function setUpBeforeClass(): void { - $normalizer = new BCP47Normalizer(); - $parser = new BCP47Parser($normalizer); - self::$registry = IanaSubtagRegistry::load($parser); + self::$registry = IanaSubtagRegistry::load(); } public function testRegistryLoading(): void @@ -26,25 +29,25 @@ public function testRegistryLoading(): void $this->assertInstanceOf(IanaSubtagRegistry::class, self::$registry); } - #[DataProvider('provideValidLanguages')] + #[DataProvider('provideLanguages')] public function testIsValidLanguage(string $language, bool $expected): void { $this->assertSame($expected, self::$registry->isValidLanguage($language)); } - #[DataProvider('provideValidScripts')] + #[DataProvider('provideScripts')] public function testIsValidScript(string $script, bool $expected): void { $this->assertSame($expected, self::$registry->isValidScript($script)); } - #[DataProvider('provideValidRegions')] + #[DataProvider('provideRegions')] public function testIsValidRegion(string $region, bool $expected): void { $this->assertSame($expected, self::$registry->isValidRegion($region)); } - #[DataProvider('provideValidVariants')] + #[DataProvider('provideVariants')] public function testIsValidVariant(string $variant, bool $expected): void { $this->assertSame($expected, self::$registry->isValidVariant($variant)); @@ -57,43 +60,99 @@ public function testIsGrandfathered(string $tag, bool $expected): void } #[DataProvider('provideValidLocales')] - public function testIsValidLocale(string $locale, bool $expected): void + public function testIsValidParsedTag(string $locale, bool $expected): void + { + $normalizer = new BCP47Normalizer(); + $parser = new BCP47Parser($normalizer); + + try { + $parsedTag = $parser->parseTag($locale); + $result = self::$registry->isValidParsedTag($parsedTag); + $this->assertSame($expected, $result); + } catch (BCP47ParserException $e) { + // If parsing fails, the locale is invalid + $this->assertFalse($expected); + } + } + + #[DataProvider('provideValidLocalesWithRequirements')] + public function testIsValidParsedTagWithRequirements(string $locale, bool $requireRegion, bool $requireScript, bool $expected): void { - $this->assertSame($expected, self::$registry->isValidLocale($locale)); + $normalizer = new BCP47Normalizer(); + $parser = new BCP47Parser($normalizer); + + try { + $parsedTag = $parser->parseTag($locale); + $result = self::$registry->isValidParsedTag($parsedTag, $requireRegion, $requireScript); + $this->assertSame($expected, $result); + } catch (BCP47ParserException $e) { + // If parsing fails, the locale is invalid + $this->assertFalse($expected); + } } - public static function provideValidLanguages(): array + #[DataProvider('provideInvalidLocales')] + public function testValidateParsedTagThrowsException(string $locale): void + { + $normalizer = new BCP47Normalizer(); + $parser = new BCP47Parser($normalizer); + + try { + $parsedTag = $parser->parseTag($locale); + $this->expectException(BCP47IanaRegistryException::class); + self::$registry->validateParsedTag($parsedTag); + } catch (BCP47ParserException $e) { + // If parsing fails, we can't test validateParsedTag + $this->markTestSkipped('Cannot test validateParsedTag because parsing failed'); + } + } + + public static function provideLanguages(): array { return [ 'valid language' => ['en', true], - 'valid language uppercase' => ['EN', true], + 'invalid language uppercase' => ['EN', false], + 'invalid language casing' => ['EN', false], 'invalid language' => ['zzzz', false], ]; } - public static function provideValidScripts(): array + /** + * 0: script + * 1: expected result + */ + public static function provideScripts(): array { return [ 'valid script' => ['Latn', true], - 'valid script lowercase' => ['latn', true], + 'invalid script lowercase' => ['latn', false], + 'invalid script uppercase' => ['LATN', false], 'valid uncoded script' => ['Zzzz', true], ]; } - public static function provideValidRegions(): array + public static function provideRegions(): array { return [ 'valid region' => ['US', true], - 'valid region lowercase' => ['us', true], + 'valid region lowercase' => ['us', false], + 'invalid region casing' => ['uS', false], 'valid unknown region' => ['ZZ', true], + 'invalid unknown region' => ['Zz', false], ]; } - public static function provideValidVariants(): array + public static function provideVariants(): array { return [ - 'valid variant' => ['1901', true], - 'invalid variant' => ['9999', false], + 'valid variant 1901' => ['1901', true], + 'valid variant 1994' => ['1994', true], + 'valid variant 1996' => ['1996', true], + 'invalid variant 1999' => ['9999', false], + 'valid viennese variant' => ['viennese', true], + 'invalid viennese variant' => ['Viennese', false], + 'valid newfound variant' => ['newfound', true], + 'invalid newfound variant' => ['NEWfound', false], ]; } @@ -116,4 +175,36 @@ public static function provideValidLocales(): array 'invalid language' => ['zz-US', false], ]; } + + public static function provideValidLocalesWithRequirements(): array + { + return [ + 'language only, no requirements' => ['en', false, false, true], + 'language only, require region' => ['en', true, false, false], + 'language only, require script' => ['en', false, true, false], + 'language only, require both' => ['en', true, true, false], + 'language-region, no requirements' => ['en-US', false, false, true], + 'language-region, require region' => ['en-US', true, false, true], + 'language-region, require script' => ['en-US', false, true, false], + 'language-region, require both' => ['en-US', true, true, false], + 'language-script, no requirements' => ['zh-Hans', false, false, true], + 'language-script, require region' => ['zh-Hans', true, false, false], + 'language-script, require script' => ['zh-Hans', false, true, true], + 'language-script, require both' => ['zh-Hans', true, true, false], + 'language-script-region, no requirements' => ['zh-Hans-CN', false, false, true], + 'language-script-region, require region' => ['zh-Hans-CN', true, false, true], + 'language-script-region, require script' => ['zh-Hans-CN', false, true, true], + 'language-script-region, require both' => ['zh-Hans-CN', true, true, true], + ]; + } + + public static function provideInvalidLocales(): array + { + return [ + 'invalid language' => ['00-US'], + 'invalid script' => ['en-0000-US'], + 'invalid region' => ['en-00'], + 'invalid variant' => ['en-US-00000'], + ]; + } } diff --git a/tests/ValueObject/LanguageTagTest.php b/tests/ValueObject/LanguageTagTest.php new file mode 100644 index 0000000..5958d27 --- /dev/null +++ b/tests/ValueObject/LanguageTagTest.php @@ -0,0 +1,73 @@ +assertInstanceOf(LanguageTag::class, $languageTag); + $this->assertSame($language, $languageTag->getLanguage()); + $this->assertSame($script, $languageTag->getScript()); + $this->assertSame($region, $languageTag->getRegion()); + $this->assertSame($variants, $languageTag->getVariants()); + } + + public static function provideValidParsedTags(): array + { + return [ + 'language only' => [ + 'en', + null, + null, + [], + ], + 'language-region' => [ + 'en', + null, + 'US', + [], + ], + 'language-script-region' => [ + 'zh', + 'Hans', + 'CN', + [], + ], + 'language-region-variant' => [ + 'de', + null, + 'DE', + ['1901'], + ], + 'language-script-region-variant' => [ + 'zh', + 'Hans', + 'CN', + ['pinyin'], + ], + ]; + } +} \ No newline at end of file