fhirsmith 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +42 -0
- package/FHIRsmith.png +0 -0
- package/README.md +277 -0
- package/config-template.json +144 -0
- package/library/folder-setup.js +58 -0
- package/library/html-server.js +166 -0
- package/library/html.js +835 -0
- package/library/i18nsupport.js +259 -0
- package/library/languages.js +779 -0
- package/library/logger-telnet.js +205 -0
- package/library/logger.js +279 -0
- package/library/package-manager.js +876 -0
- package/library/utilities.js +196 -0
- package/library/version-utilities.js +1056 -0
- package/npmprojector/config-example.json +13 -0
- package/npmprojector/indexer.js +394 -0
- package/npmprojector/npmprojector.js +395 -0
- package/npmprojector/readme.md +174 -0
- package/npmprojector/watcher.js +335 -0
- package/package.json +119 -0
- package/packages/package-crawler.js +846 -0
- package/packages/packages-template.html +126 -0
- package/packages/packages.js +2838 -0
- package/passwords.ini +2 -0
- package/publisher/publisher-template.html +208 -0
- package/publisher/publisher.js +2167 -0
- package/publisher/task-draft.js +458 -0
- package/registry/api.js +735 -0
- package/registry/crawler.js +637 -0
- package/registry/model.js +513 -0
- package/registry/readme.md +243 -0
- package/registry/registry-data.json +121015 -0
- package/registry/registry-template.html +126 -0
- package/registry/registry.js +1395 -0
- package/registry/test-runner.js +237 -0
- package/root-template.html +124 -0
- package/server.js +524 -0
- package/shl/private-key.pem +5 -0
- package/shl/public-key.pem +18 -0
- package/shl/shl.js +1125 -0
- package/shl/vhl.js +69 -0
- package/static/FHIRsmith128.png +0 -0
- package/static/FHIRsmith16.png +0 -0
- package/static/FHIRsmith32.png +0 -0
- package/static/FHIRsmith64.png +0 -0
- package/static/assets/css/bootstrap-fhir.css +5302 -0
- package/static/assets/css/bootstrap-glyphicons.css +2 -0
- package/static/assets/css/bootstrap.css +4097 -0
- package/static/assets/css/jquery-ui.css +523 -0
- package/static/assets/css/jquery-ui.structure.css +863 -0
- package/static/assets/css/jquery-ui.structure.min.css +5 -0
- package/static/assets/css/jquery-ui.theme.css +439 -0
- package/static/assets/css/jquery-ui.theme.min.css +5 -0
- package/static/assets/css/jquery.ui.all.css +7 -0
- package/static/assets/css/modules.css +18 -0
- package/static/assets/css/project.css +367 -0
- package/static/assets/css/pygments-manni.css +66 -0
- package/static/assets/css/tags.css +74 -0
- package/static/assets/css/xml.css +2 -0
- package/static/assets/fonts/glyphiconshalflings-regular.eot +0 -0
- package/static/assets/fonts/glyphiconshalflings-regular.otf +0 -0
- package/static/assets/fonts/glyphiconshalflings-regular.svg +175 -0
- package/static/assets/fonts/glyphiconshalflings-regular.ttf +0 -0
- package/static/assets/fonts/glyphiconshalflings-regular.woff +0 -0
- package/static/assets/ico/apple-touch-icon-114-precomposed.png +0 -0
- package/static/assets/ico/apple-touch-icon-144-precomposed.png +0 -0
- package/static/assets/ico/apple-touch-icon-57-precomposed.png +0 -0
- package/static/assets/ico/apple-touch-icon-72-precomposed.png +0 -0
- package/static/assets/ico/favicon.ico +0 -0
- package/static/assets/ico/favicon.png +0 -0
- package/static/assets/images/fhir-logo-www.png +0 -0
- package/static/assets/images/fhir-logo.png +0 -0
- package/static/assets/images/hl7-logo.png +0 -0
- package/static/assets/images/logo_ansinew.jpg +0 -0
- package/static/assets/images/search.png +0 -0
- package/static/assets/images/stripe.png +0 -0
- package/static/assets/images/target.png +0 -0
- package/static/assets/images/tx-registry-root.gif +0 -0
- package/static/assets/images/tx-registry.png +0 -0
- package/static/assets/images/tx-server.png +0 -0
- package/static/assets/images/tx-version.png +0 -0
- package/static/assets/js/bootstrap.min.js +6 -0
- package/static/assets/js/fhir-gw.js +259 -0
- package/static/assets/js/fhir.js +2 -0
- package/static/assets/js/html5shiv.js +8 -0
- package/static/assets/js/jcookie.js +96 -0
- package/static/assets/js/jquery-ui.min.js +6 -0
- package/static/assets/js/jquery.js +10716 -0
- package/static/assets/js/jquery.min.js +2 -0
- package/static/assets/js/jquery.ui.core.js +314 -0
- package/static/assets/js/jquery.ui.draggable.js +825 -0
- package/static/assets/js/jquery.ui.mouse.js +162 -0
- package/static/assets/js/jquery.ui.resizable.js +842 -0
- package/static/assets/js/jquery.ui.widget.js +268 -0
- package/static/assets/js/json2.js +487 -0
- package/static/assets/js/jtip.js +97 -0
- package/static/assets/js/respond.min.js +6 -0
- package/static/assets/js/statuspage.js +70 -0
- package/static/assets/js/xml.js +2 -0
- package/static/dist/js/bootstrap.js +1964 -0
- package/static/favicon.png +0 -0
- package/static/fhir.css +626 -0
- package/static/icon-fhir-16.png +0 -0
- package/static/images/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
- package/static/images/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
- package/static/images/ui-bg_flat_10_000000_40x100.png +0 -0
- package/static/images/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
- package/static/images/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
- package/static/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- package/static/images/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
- package/static/images/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
- package/static/images/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
- package/static/images/ui-icons_222222_256x240.png +0 -0
- package/static/images/ui-icons_228ef1_256x240.png +0 -0
- package/static/images/ui-icons_ef8c08_256x240.png +0 -0
- package/static/images/ui-icons_ffd27a_256x240.png +0 -0
- package/static/images/ui-icons_ffffff_256x240.png +0 -0
- package/static/js/jquery.effects.blind.js +49 -0
- package/static/js/jquery.effects.bounce.js +78 -0
- package/static/js/jquery.effects.clip.js +54 -0
- package/static/js/jquery.effects.core.js +763 -0
- package/static/js/jquery.effects.drop.js +50 -0
- package/static/js/jquery.effects.explode.js +79 -0
- package/static/js/jquery.effects.fade.js +32 -0
- package/static/js/jquery.effects.fold.js +56 -0
- package/static/js/jquery.effects.highlight.js +50 -0
- package/static/js/jquery.effects.pulsate.js +51 -0
- package/static/js/jquery.effects.scale.js +178 -0
- package/static/js/jquery.effects.shake.js +57 -0
- package/static/js/jquery.effects.slide.js +50 -0
- package/static/js/jquery.effects.transfer.js +45 -0
- package/static/js/jquery.ui.accordion.js +611 -0
- package/static/js/jquery.ui.autocomplete.js +612 -0
- package/static/js/jquery.ui.button.js +416 -0
- package/static/js/jquery.ui.datepicker.js +1823 -0
- package/static/js/jquery.ui.dialog.js +878 -0
- package/static/js/jquery.ui.droppable.js +296 -0
- package/static/js/jquery.ui.position.js +252 -0
- package/static/js/jquery.ui.progressbar.js +109 -0
- package/static/js/jquery.ui.selectable.js +266 -0
- package/static/js/jquery.ui.slider.js +666 -0
- package/static/js/jquery.ui.sortable.js +1077 -0
- package/static/js/jquery.ui.tabs.js +758 -0
- package/stats.js +80 -0
- package/test-cache/vsac/vsac-valuesets.db +0 -0
- package/token/nginx_passport_setup.md +383 -0
- package/token/security_guide.md +294 -0
- package/token/token-template.html +330 -0
- package/token/token.js +1300 -0
- package/translations/Messages.properties +1510 -0
- package/translations/Messages_ar.properties +1399 -0
- package/translations/Messages_de.properties +836 -0
- package/translations/Messages_es.properties +737 -0
- package/translations/Messages_fr.properties +1 -0
- package/translations/Messages_ja.properties +893 -0
- package/translations/Messages_nl.properties +1357 -0
- package/translations/Messages_pt.properties +1302 -0
- package/translations/Messages_ru.properties +1 -0
- package/translations/Messages_uz.properties +1 -0
- package/translations/Messages_zh.properties +1 -0
- package/translations/rendering-phrases.properties +1128 -0
- package/translations/rendering-phrases_ar.properties +1091 -0
- package/translations/rendering-phrases_de.properties +6 -0
- package/translations/rendering-phrases_es.properties +6 -0
- package/translations/rendering-phrases_fr.properties +624 -0
- package/translations/rendering-phrases_ja.properties +21 -0
- package/translations/rendering-phrases_nl.properties +970 -0
- package/translations/rendering-phrases_pt.properties +1020 -0
- package/translations/rendering-phrases_ru.properties +1094 -0
- package/translations/rendering-phrases_uz.properties +1 -0
- package/translations/rendering-phrases_zh.properties +1 -0
- package/tx/README.md +418 -0
- package/tx/cm/cm-api.js +110 -0
- package/tx/cm/cm-database.js +735 -0
- package/tx/cm/cm-package.js +325 -0
- package/tx/cs/cs-api.js +789 -0
- package/tx/cs/cs-areacode.js +615 -0
- package/tx/cs/cs-country.js +1110 -0
- package/tx/cs/cs-cpt.js +785 -0
- package/tx/cs/cs-cs.js +1579 -0
- package/tx/cs/cs-currency.js +539 -0
- package/tx/cs/cs-db.js +1321 -0
- package/tx/cs/cs-hgvs.js +329 -0
- package/tx/cs/cs-lang.js +465 -0
- package/tx/cs/cs-loinc.js +1485 -0
- package/tx/cs/cs-mimetypes.js +238 -0
- package/tx/cs/cs-ndc.js +704 -0
- package/tx/cs/cs-omop.js +1025 -0
- package/tx/cs/cs-provider-api.js +43 -0
- package/tx/cs/cs-provider-list.js +37 -0
- package/tx/cs/cs-rxnorm.js +808 -0
- package/tx/cs/cs-snomed.js +1102 -0
- package/tx/cs/cs-ucum.js +514 -0
- package/tx/cs/cs-unii.js +271 -0
- package/tx/cs/cs-uri.js +218 -0
- package/tx/cs/cs-usstates.js +305 -0
- package/tx/dev.fhir.org.yml +14 -0
- package/tx/fixtures/test-cases-setup.json +18 -0
- package/tx/fixtures/test-cases.yml +16 -0
- package/tx/html/codesystem-operations.liquid +25 -0
- package/tx/html/home-metrics.liquid +247 -0
- package/tx/html/operations-form.liquid +148 -0
- package/tx/html/search-form.liquid +62 -0
- package/tx/html/tx-template.html +133 -0
- package/tx/html/valueset-operations.liquid +54 -0
- package/tx/importers/atc-to-fhir.js +316 -0
- package/tx/importers/import-loinc.module.js +1536 -0
- package/tx/importers/import-ndc.module.js +1088 -0
- package/tx/importers/import-rxnorm.module.js +898 -0
- package/tx/importers/import-sct.module.js +2457 -0
- package/tx/importers/import-unii.module.js +601 -0
- package/tx/importers/readme.md +453 -0
- package/tx/importers/subset-loinc.module.js +1081 -0
- package/tx/importers/subset-rxnorm.module.js +938 -0
- package/tx/importers/tx-import-base.js +351 -0
- package/tx/importers/tx-import-settings.js +310 -0
- package/tx/importers/tx-import.js +357 -0
- package/tx/library/canonical-resource.js +88 -0
- package/tx/library/capabilitystatement.js +292 -0
- package/tx/library/codesystem.js +774 -0
- package/tx/library/conceptmap.js +568 -0
- package/tx/library/designations.js +932 -0
- package/tx/library/errors.js +77 -0
- package/tx/library/extensions.js +117 -0
- package/tx/library/namingsystem.js +322 -0
- package/tx/library/operation-outcome.js +127 -0
- package/tx/library/parameters.js +105 -0
- package/tx/library/renderer.js +1559 -0
- package/tx/library/terminologycapabilities.js +418 -0
- package/tx/library/ucum-parsers.js +1029 -0
- package/tx/library/ucum-service.js +370 -0
- package/tx/library/ucum-types.js +1099 -0
- package/tx/library/valueset.js +543 -0
- package/tx/library.js +676 -0
- package/tx/ocl/cm-ocl.js +106 -0
- package/tx/ocl/cs-ocl.js +39 -0
- package/tx/ocl/vs-ocl.js +105 -0
- package/tx/operation-context.js +568 -0
- package/tx/params.js +613 -0
- package/tx/provider.js +403 -0
- package/tx/sct/ecl.js +1560 -0
- package/tx/sct/expressions.js +2077 -0
- package/tx/sct/structures.js +1396 -0
- package/tx/tx-html.js +1063 -0
- package/tx/tx.fhir.org.yml +39 -0
- package/tx/tx.js +927 -0
- package/tx/vs/vs-api.js +112 -0
- package/tx/vs/vs-database.js +786 -0
- package/tx/vs/vs-package.js +358 -0
- package/tx/vs/vs-vsac.js +366 -0
- package/tx/workers/batch-validate.js +129 -0
- package/tx/workers/batch.js +361 -0
- package/tx/workers/closure.js +32 -0
- package/tx/workers/expand.js +1845 -0
- package/tx/workers/lookup.js +407 -0
- package/tx/workers/metadata.js +467 -0
- package/tx/workers/operations.js +34 -0
- package/tx/workers/read.js +164 -0
- package/tx/workers/search.js +384 -0
- package/tx/workers/subsumes.js +334 -0
- package/tx/workers/translate.js +492 -0
- package/tx/workers/validate.js +2504 -0
- package/tx/workers/worker.js +904 -0
- package/tx/xml/capabilitystatement-xml.js +63 -0
- package/tx/xml/codesystem-xml.js +62 -0
- package/tx/xml/conceptmap-xml.js +65 -0
- package/tx/xml/namingsystem-xml.js +65 -0
- package/tx/xml/operationoutcome-xml.js +127 -0
- package/tx/xml/parameters-xml.js +312 -0
- package/tx/xml/terminologycapabilities-xml.js +64 -0
- package/tx/xml/valueset-xml.js +64 -0
- package/tx/xml/xml-base.js +603 -0
- package/vcl/vcl-parser.js +1098 -0
- package/vcl/vcl.js +253 -0
- package/windows-install.js +19 -0
- package/xig/xig-template.html +124 -0
- package/xig/xig.js +3049 -0
|
@@ -0,0 +1,779 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const os = require('os');
|
|
3
|
+
const {validateParameter, validateOptionalParameter, Utilities} = require("./utilities");
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Language part types for matching depth
|
|
7
|
+
*/
|
|
8
|
+
const LanguagePartType = {
|
|
9
|
+
NONE: 0,
|
|
10
|
+
LANGUAGE: 1,
|
|
11
|
+
EXTLANG: 2,
|
|
12
|
+
SCRIPT: 3,
|
|
13
|
+
REGION: 4,
|
|
14
|
+
VARIANT: 5,
|
|
15
|
+
EXTENSION: 6
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Base class for language entries
|
|
20
|
+
*/
|
|
21
|
+
class LanguageEntry {
|
|
22
|
+
constructor() {
|
|
23
|
+
this.code = '';
|
|
24
|
+
this.displays = [];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Language definition entry
|
|
30
|
+
*/
|
|
31
|
+
class LanguageLanguage extends LanguageEntry {
|
|
32
|
+
constructor() {
|
|
33
|
+
super();
|
|
34
|
+
this.suppressScript = '';
|
|
35
|
+
this.scope = '';
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Extended language definition entry
|
|
41
|
+
*/
|
|
42
|
+
class LanguageExtLang extends LanguageEntry {}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Script definition entry
|
|
46
|
+
*/
|
|
47
|
+
class LanguageScript extends LanguageEntry {}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Region definition entry
|
|
51
|
+
*/
|
|
52
|
+
class LanguageRegion extends LanguageEntry {}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Variant definition entry
|
|
56
|
+
*/
|
|
57
|
+
class LanguageVariant extends LanguageEntry {}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Individual language representation based on BCP 47
|
|
61
|
+
*/
|
|
62
|
+
class Language {
|
|
63
|
+
constructor(code = '', languageDefinitions = null) {
|
|
64
|
+
this.code = code;
|
|
65
|
+
this.language = '';
|
|
66
|
+
this.extLang = [];
|
|
67
|
+
this.script = '';
|
|
68
|
+
this.region = '';
|
|
69
|
+
this.variant = '';
|
|
70
|
+
this.extension = '';
|
|
71
|
+
this.privateUse = [];
|
|
72
|
+
this.quality = 1.0; // For Accept-Language header quality values
|
|
73
|
+
|
|
74
|
+
if (this.code) {
|
|
75
|
+
this._parse(languageDefinitions);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Get system default language, fallback to en-US
|
|
81
|
+
*/
|
|
82
|
+
static _getDefaultLanguage() {
|
|
83
|
+
try {
|
|
84
|
+
// Try to get system locale
|
|
85
|
+
const locale = Intl.DateTimeFormat().resolvedOptions().locale ||
|
|
86
|
+
process.env.LANG ||
|
|
87
|
+
process.env.LANGUAGE ||
|
|
88
|
+
os.platform() === 'win32' ? 'en-US' : 'en-US';
|
|
89
|
+
|
|
90
|
+
return locale.replace('_', '-'); // Convert underscore to hyphen
|
|
91
|
+
} catch (error) {
|
|
92
|
+
return 'en-US';
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Create Language from xml:lang attribute
|
|
98
|
+
*/
|
|
99
|
+
static fromXmlLang(xmlLang) {
|
|
100
|
+
return new Language(xmlLang || 'en-US');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Create Language from system default
|
|
105
|
+
*/
|
|
106
|
+
static fromSystemDefault() {
|
|
107
|
+
return new Language(this._getDefaultLanguage());
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Parse the language code according to BCP 47
|
|
112
|
+
*/
|
|
113
|
+
_parse(languageDefinitions) {
|
|
114
|
+
if (!this.code) return;
|
|
115
|
+
|
|
116
|
+
const parts = this.code.split('-');
|
|
117
|
+
let index = 0;
|
|
118
|
+
|
|
119
|
+
// Language (required)
|
|
120
|
+
if (index < parts.length) {
|
|
121
|
+
this.language = parts[index];
|
|
122
|
+
if (this.language != '*' && languageDefinitions && !languageDefinitions.languages.has(this.language)) {
|
|
123
|
+
throw new Error("The language '"+this.language+"' in the code '"+this.language+"' is not valid");
|
|
124
|
+
}
|
|
125
|
+
index++;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Extended language (up to 3)
|
|
129
|
+
for (let i = 0; i < 3 && index < parts.length; i++) {
|
|
130
|
+
const part = parts[index];
|
|
131
|
+
if (part.length === 3 && /^[a-zA-Z]{3}$/.test(part)) {
|
|
132
|
+
if (languageDefinitions && !languageDefinitions.extLanguages.has(part)) {
|
|
133
|
+
throw new Error("The extLanguage '"+part+"' in the code '"+code+"' is not valid");
|
|
134
|
+
}
|
|
135
|
+
this.extLang.push(part.toLowerCase());
|
|
136
|
+
index++;
|
|
137
|
+
} else {
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Script (4 letters)
|
|
143
|
+
if (index < parts.length) {
|
|
144
|
+
const part = parts[index];
|
|
145
|
+
if (part.length === 4 && /^[a-zA-Z]{4}$/.test(part)) {
|
|
146
|
+
if (languageDefinitions && !languageDefinitions.scripts.has(part)) {
|
|
147
|
+
throw new Error("The script '"+part+"' in the code '"+code+"' is not valid");
|
|
148
|
+
}
|
|
149
|
+
this.script = part.charAt(0).toUpperCase() + part.slice(1).toLowerCase();
|
|
150
|
+
index++;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Region (2 letters or 3 digits)
|
|
155
|
+
if (index < parts.length) {
|
|
156
|
+
const part = parts[index];
|
|
157
|
+
if ((part.length === 2 && /^[a-zA-Z]{2}$/.test(part)) ||
|
|
158
|
+
(part.length === 3 && /^[0-9]{3}$/.test(part))) {
|
|
159
|
+
if (languageDefinitions && !languageDefinitions.regions.has(part)) {
|
|
160
|
+
throw new Error("The region '"+part+"' in the code '"+code+"' is not valid");
|
|
161
|
+
}
|
|
162
|
+
this.region = part.toUpperCase();
|
|
163
|
+
index++;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Variant (5-8 chars or 4 chars starting with digit)
|
|
168
|
+
if (index < parts.length) {
|
|
169
|
+
const part = parts[index];
|
|
170
|
+
if ((part.length >= 5 && part.length <= 8) ||
|
|
171
|
+
(part.length === 4 && /^[0-9]/.test(part))) {
|
|
172
|
+
this.variant = part.toLowerCase();
|
|
173
|
+
index++;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Extensions and private use
|
|
178
|
+
while (index < parts.length) {
|
|
179
|
+
const part = parts[index];
|
|
180
|
+
if (part === 'x') {
|
|
181
|
+
// Private use
|
|
182
|
+
index++;
|
|
183
|
+
while (index < parts.length) {
|
|
184
|
+
this.privateUse.push(parts[index]);
|
|
185
|
+
index++;
|
|
186
|
+
}
|
|
187
|
+
} else if (part.length === 1 && part !== 'x') {
|
|
188
|
+
// Extension
|
|
189
|
+
this.extension = part + '-' + parts.slice(index + 1).join('-');
|
|
190
|
+
break;
|
|
191
|
+
} else {
|
|
192
|
+
index++;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Check if this language matches another to a given depth
|
|
199
|
+
*/
|
|
200
|
+
matches(other, depth = LanguagePartType.LANGUAGE) {
|
|
201
|
+
if (!other) return false;
|
|
202
|
+
|
|
203
|
+
if (depth >= LanguagePartType.EXTENSION) {
|
|
204
|
+
if (this.extension !== other.extension) return false;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (depth >= LanguagePartType.VARIANT) {
|
|
208
|
+
if (this.variant !== other.variant) return false;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (depth >= LanguagePartType.REGION) {
|
|
212
|
+
if (this.region !== other.region) return false;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (depth >= LanguagePartType.SCRIPT) {
|
|
216
|
+
if (this.script !== other.script) return false;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (depth >= LanguagePartType.EXTLANG) {
|
|
220
|
+
if (JSON.stringify(this.extLang) !== JSON.stringify(other.extLang)) return false;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (depth >= LanguagePartType.LANGUAGE) {
|
|
224
|
+
return this.language === other.language;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Simple matching (all non-empty parts must match)
|
|
232
|
+
*/
|
|
233
|
+
matchesSimple(other) {
|
|
234
|
+
if (!other) return false;
|
|
235
|
+
|
|
236
|
+
if (this.extension && this.extension !== other.extension) return false;
|
|
237
|
+
if (this.variant && this.variant !== other.variant) return false;
|
|
238
|
+
if (this.region && this.region !== other.region) return false;
|
|
239
|
+
if (this.script && this.script !== other.script) return false;
|
|
240
|
+
if (this.extLang.length > 0 &&
|
|
241
|
+
JSON.stringify(this.extLang) !== JSON.stringify(other.extLang)) return false;
|
|
242
|
+
|
|
243
|
+
return this.language === other.language;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Check if this is a simple language-region tag
|
|
248
|
+
*/
|
|
249
|
+
isLangRegion() {
|
|
250
|
+
return (this.language !== '' && this.region !== '' &&
|
|
251
|
+
this.extLang.length === 0 && this.script === '' &&
|
|
252
|
+
this.variant === '' && this.extension === '' && this.privateUse.length === 0);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Check if this language matches another according to display matching rules:
|
|
257
|
+
* - Same language (e.g. en = en)
|
|
258
|
+
* - This language has more details than the target (e.g. en-AU matches en)
|
|
259
|
+
* - Either is blank and the other is en or en-US
|
|
260
|
+
*/
|
|
261
|
+
matchesForDisplay(other) {
|
|
262
|
+
if (!other) return false;
|
|
263
|
+
|
|
264
|
+
const thisLang = typeof other === 'string' ? new Language(other) : other;
|
|
265
|
+
|
|
266
|
+
// Handle blank languages - match only if other is exactly 'en' or 'en-US'
|
|
267
|
+
if (!this.language || this.language === '') {
|
|
268
|
+
return thisLang.code === 'en' || thisLang.code === 'en-US';
|
|
269
|
+
}
|
|
270
|
+
if (!thisLang.language || thisLang.language === '') {
|
|
271
|
+
return this.code === 'en' || this.code === 'en-US';
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Must match at language level
|
|
275
|
+
if (this.language !== thisLang.language) {
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Check each component: if target specifies it, this must match exactly
|
|
280
|
+
// If target doesn't specify it, this can have any value (more specific is OK)
|
|
281
|
+
|
|
282
|
+
// Check script
|
|
283
|
+
if (thisLang.script && thisLang.script !== '') {
|
|
284
|
+
if (this.script !== thisLang.script) {
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Check region
|
|
290
|
+
if (thisLang.region && thisLang.region !== '') {
|
|
291
|
+
if (this.region !== thisLang.region) {
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Check variant
|
|
297
|
+
if (thisLang.variant && thisLang.variant !== '') {
|
|
298
|
+
if (this.variant !== thisLang.variant) {
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return true;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
isEnglishOrNothing() {
|
|
308
|
+
return !this.code || this.code === 'en' || this.code === 'en-US';
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
toString() {
|
|
312
|
+
const parts = [];
|
|
313
|
+
|
|
314
|
+
if (this.language) parts.push(this.language);
|
|
315
|
+
parts.push(...this.extLang);
|
|
316
|
+
if (this.script) parts.push(this.script);
|
|
317
|
+
if (this.region) parts.push(this.region);
|
|
318
|
+
if (this.variant) parts.push(this.variant);
|
|
319
|
+
if (this.extension) parts.push(this.extension);
|
|
320
|
+
if (this.privateUse.length > 0) {
|
|
321
|
+
parts.push('x');
|
|
322
|
+
parts.push(...this.privateUse);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return parts.join('-');
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
asString() {
|
|
329
|
+
if (this.quality != undefined && this.quality != 1) {
|
|
330
|
+
return this.code+"; q="+this.quality;
|
|
331
|
+
} else {
|
|
332
|
+
return this.code;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Collection of languages with preference ordering
|
|
339
|
+
*/
|
|
340
|
+
class Languages {
|
|
341
|
+
definitions;
|
|
342
|
+
|
|
343
|
+
constructor(definitions) {
|
|
344
|
+
validateOptionalParameter(definitions, "definitions", LanguageDefinitions);
|
|
345
|
+
this.definitions = definitions;
|
|
346
|
+
this.languages = [];
|
|
347
|
+
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Parse Accept-Language header
|
|
352
|
+
* Format: "en-US,en;q=0.9,fr;q=0.8"
|
|
353
|
+
*/
|
|
354
|
+
static fromAcceptLanguage(acceptLanguageHeader, languageDefinitions, addWildcard) {
|
|
355
|
+
const languages = new Languages(languageDefinitions);
|
|
356
|
+
let wc = false;
|
|
357
|
+
|
|
358
|
+
if (!acceptLanguageHeader) {
|
|
359
|
+
languages.add(Language.fromSystemDefault());
|
|
360
|
+
return languages;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const entries = acceptLanguageHeader.split(',').map(s => s.trim());
|
|
364
|
+
const parsed = [];
|
|
365
|
+
|
|
366
|
+
for (const entry of entries) {
|
|
367
|
+
const [langCode, qValue] = entry.split(';');
|
|
368
|
+
const quality = qValue ? Utilities.parseFloatOrDefault(qValue.split('=')[1], 1.0) : 1.0;
|
|
369
|
+
|
|
370
|
+
let lc = langCode.trim();
|
|
371
|
+
wc = wc || lc == '*';
|
|
372
|
+
const lang = new Language(lc, languageDefinitions);
|
|
373
|
+
lang.quality = quality;
|
|
374
|
+
parsed.push(lang);
|
|
375
|
+
}
|
|
376
|
+
if (addWildcard && !wc) {
|
|
377
|
+
const lang = new Language('*', languageDefinitions);
|
|
378
|
+
lang.quality = 0.01;
|
|
379
|
+
lang.implicit = true;
|
|
380
|
+
parsed.push(lang);
|
|
381
|
+
}
|
|
382
|
+
// Sort by quality (descending)
|
|
383
|
+
parsed.sort((a, b) => b.quality - a.quality);
|
|
384
|
+
|
|
385
|
+
for (const lang of parsed) {
|
|
386
|
+
languages.add(lang);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return languages;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Add a language to the collection
|
|
394
|
+
*/
|
|
395
|
+
add(language) {
|
|
396
|
+
this.languages.push(language);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Get iterator for languages in preference order
|
|
401
|
+
*/
|
|
402
|
+
[Symbol.iterator]() {
|
|
403
|
+
return this.languages[Symbol.iterator]();
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Get language by index
|
|
408
|
+
*/
|
|
409
|
+
get(index) {
|
|
410
|
+
return this.languages[index];
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Get number of languages
|
|
415
|
+
*/
|
|
416
|
+
get length() {
|
|
417
|
+
return this.languages.length;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Find best match for a given language
|
|
422
|
+
*/
|
|
423
|
+
findBestMatch(target, depth = LanguagePartType.LANGUAGE) {
|
|
424
|
+
for (const lang of this.languages) {
|
|
425
|
+
if (lang.matches(target, depth)) {
|
|
426
|
+
return lang;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
return null;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Check if any language matches the target
|
|
434
|
+
*/
|
|
435
|
+
matches(target, depth = LanguagePartType.LANGUAGE) {
|
|
436
|
+
return this.findBestMatch(target, depth) !== null;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Get primary language (first in preference order)
|
|
441
|
+
*/
|
|
442
|
+
getPrimary() {
|
|
443
|
+
return this.languages.length > 0 ? this.languages[0] : new Language('en-US');
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
isEnglishOrNothing() {
|
|
447
|
+
for (const lang of this.languages) {
|
|
448
|
+
if (!lang.isEnglishOrNothing()) {
|
|
449
|
+
return false;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
return true;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Convert to string representation (similar to Accept-Language header format)
|
|
457
|
+
*/
|
|
458
|
+
toString() {
|
|
459
|
+
if (this.languages.length === 0) {
|
|
460
|
+
return '';
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
return this.languages.map(lang => {
|
|
464
|
+
if (lang.quality === 1.0) {
|
|
465
|
+
return lang.code;
|
|
466
|
+
} else {
|
|
467
|
+
return `${lang.code};q=${lang.quality}`;
|
|
468
|
+
}
|
|
469
|
+
}).join(',');
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
asString(incWildcard) {
|
|
473
|
+
const parts = [];
|
|
474
|
+
for (const lang of this.languages) {
|
|
475
|
+
if ((incWildcard || lang.code !== '*') && !lang.implicit) {
|
|
476
|
+
parts.push(lang.asString());
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
return parts.join(', ');
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Global language definitions loaded from IETF registry
|
|
485
|
+
*/
|
|
486
|
+
class LanguageDefinitions {
|
|
487
|
+
constructor() {
|
|
488
|
+
this.languages = new Map();
|
|
489
|
+
this.extLanguages = new Map();
|
|
490
|
+
this.scripts = new Map();
|
|
491
|
+
this.regions = new Map();
|
|
492
|
+
this.variants = new Map();
|
|
493
|
+
this.parsed = new Map(); // Cache for parsed languages
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Load definitions from IETF language subtag registry file
|
|
498
|
+
*/
|
|
499
|
+
static async fromFile(filePath) {
|
|
500
|
+
const definitions = new LanguageDefinitions();
|
|
501
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
502
|
+
definitions._load(content);
|
|
503
|
+
return definitions;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Load definitions from content string
|
|
508
|
+
*/
|
|
509
|
+
static fromContent(content) {
|
|
510
|
+
const definitions = new LanguageDefinitions();
|
|
511
|
+
definitions._load(content);
|
|
512
|
+
return definitions;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Check if source content is valid
|
|
517
|
+
*/
|
|
518
|
+
static checkSource(source) {
|
|
519
|
+
return source.startsWith('%%') ? 'Ok' : 'Invalid';
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Parse the IETF registry format
|
|
524
|
+
*/
|
|
525
|
+
_load(source) {
|
|
526
|
+
const lines = source.split('\n');
|
|
527
|
+
let i = 0;
|
|
528
|
+
|
|
529
|
+
while (i < lines.length) {
|
|
530
|
+
if (lines[i].trim() === '%%') {
|
|
531
|
+
i++;
|
|
532
|
+
const [vars, nextIndex] = this._readVars(lines, i);
|
|
533
|
+
i = nextIndex;
|
|
534
|
+
|
|
535
|
+
switch (vars.Type) {
|
|
536
|
+
case 'language':
|
|
537
|
+
this._loadLanguage(vars);
|
|
538
|
+
break;
|
|
539
|
+
case 'extlang':
|
|
540
|
+
this._loadExtLang(vars);
|
|
541
|
+
break;
|
|
542
|
+
case 'script':
|
|
543
|
+
this._loadScript(vars);
|
|
544
|
+
break;
|
|
545
|
+
case 'region':
|
|
546
|
+
this._loadRegion(vars);
|
|
547
|
+
break;
|
|
548
|
+
case 'variant':
|
|
549
|
+
this._loadVariant(vars);
|
|
550
|
+
break;
|
|
551
|
+
case 'grandfathered':
|
|
552
|
+
case 'redundant':
|
|
553
|
+
// Skip these for now
|
|
554
|
+
break;
|
|
555
|
+
default:
|
|
556
|
+
throw new Error(`Unknown type: ${vars.Type} at line ${i + 1}`);
|
|
557
|
+
}
|
|
558
|
+
} else {
|
|
559
|
+
i++;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Read variables from the registry format
|
|
566
|
+
*/
|
|
567
|
+
_readVars(lines, startIndex) {
|
|
568
|
+
const vars = {};
|
|
569
|
+
let i = startIndex;
|
|
570
|
+
|
|
571
|
+
while (i < lines.length && lines[i].trim() !== '%%') {
|
|
572
|
+
const line = lines[i].trim();
|
|
573
|
+
if (line && !line.startsWith(' ')) {
|
|
574
|
+
const colonIndex = line.indexOf(':');
|
|
575
|
+
if (colonIndex > 0) {
|
|
576
|
+
const key = line.substring(0, colonIndex).trim();
|
|
577
|
+
const value = line.substring(colonIndex + 1).trim();
|
|
578
|
+
|
|
579
|
+
if (vars[key]) {
|
|
580
|
+
vars[key] += '|' + value;
|
|
581
|
+
} else {
|
|
582
|
+
vars[key] = value;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
i++;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
return [vars, i];
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* Load language entry
|
|
594
|
+
*/
|
|
595
|
+
_loadLanguage(vars) {
|
|
596
|
+
const lang = new LanguageLanguage();
|
|
597
|
+
lang.code = vars.Subtag;
|
|
598
|
+
lang.displays = vars.Description ? vars.Description.split('|') : [];
|
|
599
|
+
lang.suppressScript = vars['Suppress-Script'] || '';
|
|
600
|
+
lang.scope = vars.Scope || '';
|
|
601
|
+
|
|
602
|
+
if (this.languages.has(lang.code)) {
|
|
603
|
+
throw new Error(`Duplicate language code: ${lang.code}`);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
this.languages.set(lang.code, lang);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Load extended language entry
|
|
611
|
+
*/
|
|
612
|
+
_loadExtLang(vars) {
|
|
613
|
+
const extLang = new LanguageExtLang();
|
|
614
|
+
extLang.code = vars.Subtag;
|
|
615
|
+
extLang.displays = vars.Description ? vars.Description.split('|') : [];
|
|
616
|
+
|
|
617
|
+
if (this.extLanguages.has(extLang.code)) {
|
|
618
|
+
throw new Error(`Duplicate extlang code: ${extLang.code}`);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
this.extLanguages.set(extLang.code, extLang);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* Load script entry
|
|
626
|
+
*/
|
|
627
|
+
_loadScript(vars) {
|
|
628
|
+
const script = new LanguageScript();
|
|
629
|
+
script.code = vars.Subtag;
|
|
630
|
+
script.displays = vars.Description ? vars.Description.split('|') : [];
|
|
631
|
+
|
|
632
|
+
if (this.scripts.has(script.code)) {
|
|
633
|
+
throw new Error(`Duplicate script code: ${script.code}`);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
this.scripts.set(script.code, script);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* Load region entry
|
|
641
|
+
*/
|
|
642
|
+
_loadRegion(vars) {
|
|
643
|
+
const region = new LanguageRegion();
|
|
644
|
+
region.code = vars.Subtag;
|
|
645
|
+
region.displays = vars.Description ? vars.Description.split('|') : [];
|
|
646
|
+
|
|
647
|
+
if (this.regions.has(region.code)) {
|
|
648
|
+
throw new Error(`Duplicate region code: ${region.code}`);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
this.regions.set(region.code, region);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* Load variant entry
|
|
656
|
+
*/
|
|
657
|
+
_loadVariant(vars) {
|
|
658
|
+
const variant = new LanguageVariant();
|
|
659
|
+
variant.code = vars.Subtag;
|
|
660
|
+
variant.displays = vars.Description ? vars.Description.split('|') : [];
|
|
661
|
+
|
|
662
|
+
if (this.variants.has(variant.code)) {
|
|
663
|
+
throw new Error(`Duplicate variant code: ${variant.code}`);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
this.variants.set(variant.code, variant);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* Parse and validate a language code
|
|
671
|
+
*
|
|
672
|
+
* @return {Language} parsed language (or null)
|
|
673
|
+
*/
|
|
674
|
+
parse(code) {
|
|
675
|
+
if (!code) return null;
|
|
676
|
+
|
|
677
|
+
// Check cache first
|
|
678
|
+
if (this.parsed.has(code)) {
|
|
679
|
+
return this.parsed.get(code);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
const parts = code.split('-');
|
|
683
|
+
let index = 0;
|
|
684
|
+
|
|
685
|
+
// Validate language
|
|
686
|
+
if (index >= parts.length) return null;
|
|
687
|
+
const langCode = parts[index].toLowerCase();
|
|
688
|
+
if (!this.languages.has(langCode) && langCode !== '*') {
|
|
689
|
+
return null; // Invalid language code
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
const lang = new Language(code);
|
|
693
|
+
|
|
694
|
+
// Cache the result
|
|
695
|
+
this.parsed.set(code, lang);
|
|
696
|
+
return lang;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
/**
|
|
700
|
+
* Get display name for language
|
|
701
|
+
*/
|
|
702
|
+
getDisplayForLang(code, displayIndex = 0) {
|
|
703
|
+
const lang = this.languages.get(code);
|
|
704
|
+
return lang && lang.displays[displayIndex] ? lang.displays[displayIndex] : code;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
/**
|
|
708
|
+
* Get display name for region
|
|
709
|
+
*/
|
|
710
|
+
getDisplayForRegion(code, displayIndex = 0) {
|
|
711
|
+
const region = this.regions.get(code);
|
|
712
|
+
return region && region.displays[displayIndex] ? region.displays[displayIndex] : code;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* Get display name for script
|
|
717
|
+
*/
|
|
718
|
+
getDisplayForScript(code, displayIndex = 0) {
|
|
719
|
+
const script = this.scripts.get(code);
|
|
720
|
+
return script && script.displays[displayIndex] ? script.displays[displayIndex] : code;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
/**
|
|
724
|
+
* Present a language with full display names
|
|
725
|
+
*/
|
|
726
|
+
present(lang, displayIndex = 0, template = null) {
|
|
727
|
+
if (!lang) return '';
|
|
728
|
+
|
|
729
|
+
if (template) {
|
|
730
|
+
return template
|
|
731
|
+
.replace('{{lang}}', this.getDisplayForLang(lang.language, displayIndex))
|
|
732
|
+
.replace('{{region}}', this.getDisplayForRegion(lang.region, displayIndex))
|
|
733
|
+
.replace('{{script}}', this.getDisplayForScript(lang.script, displayIndex));
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
let result = this.getDisplayForLang(lang.language, displayIndex);
|
|
737
|
+
|
|
738
|
+
const parts = [];
|
|
739
|
+
if (lang.script) {
|
|
740
|
+
parts.push(`Script=${this.getDisplayForScript(lang.script, 0)}`);
|
|
741
|
+
}
|
|
742
|
+
if (lang.region) {
|
|
743
|
+
parts.push(`Region=${this.getDisplayForRegion(lang.region, 0)}`);
|
|
744
|
+
}
|
|
745
|
+
if (lang.variant) {
|
|
746
|
+
const variant = this.variants.get(lang.variant);
|
|
747
|
+
const variantDisplay = variant && variant.displays[0] ? variant.displays[0] : lang.variant;
|
|
748
|
+
parts.push(`Variant=${variantDisplay}`);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
if (parts.length > 0) {
|
|
752
|
+
result += ` (${parts.join(', ')})`;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
return result;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
/**
|
|
759
|
+
* Get number of available displays for a language
|
|
760
|
+
*/
|
|
761
|
+
displayCount(lang) {
|
|
762
|
+
if (!lang) return 0;
|
|
763
|
+
const language = this.languages.get(lang.language);
|
|
764
|
+
return language ? language.displays.length : 0;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
module.exports = {
|
|
769
|
+
Language,
|
|
770
|
+
Languages,
|
|
771
|
+
LanguageDefinitions,
|
|
772
|
+
LanguagePartType,
|
|
773
|
+
LanguageEntry,
|
|
774
|
+
LanguageLanguage,
|
|
775
|
+
LanguageExtLang,
|
|
776
|
+
LanguageScript,
|
|
777
|
+
LanguageRegion,
|
|
778
|
+
LanguageVariant
|
|
779
|
+
};
|