Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
827750d
fixes
Feb 10, 2026
013d1a7
fixes
Feb 10, 2026
869a200
add languages for languages
Feb 10, 2026
c146384
rework cross version handling
Feb 10, 2026
b630d06
fix version handling more
Feb 10, 2026
d881df2
utiliites for processing comparison files on server
Feb 10, 2026
59d4441
fix exclude processing (#77)
Feb 10, 2026
c48e29f
fix wrong error location (#106)
Feb 10, 2026
acdd4f6
Don't crash on text only CodeableConcept (#78)
Feb 10, 2026
a0b16b6
fix POST with non-Parameters resourceType crashes server (#80)
Feb 10, 2026
296aa87
fix handling wrong media type (#81)
Feb 10, 2026
082fa15
fix return mime type (87)
Feb 10, 2026
b0f6e88
fix closure handler (#101)
Feb 10, 2026
2c0ad71
improved media type support (#105)
Feb 10, 2026
1d1b272
implement _format parameter (#85)
Feb 10, 2026
15fa7ff
refactor supplement handling (#104)
Feb 10, 2026
4c2cbda
fix handling of inferSystem (#68)
Feb 10, 2026
208380f
fix value truncated in error messages (#103)
Feb 10, 2026
723b936
fix $subsumes returns not-subsumed for identical codes (#71)
Feb 10, 2026
0dff129
fix abstract code handling ($65)
Feb 10, 2026
f80b4ce
improve system inference in the presence of filters (#69)
Feb 10, 2026
651d468
fix inline codeSystem resource parameter crashes with contentMode err…
Feb 10, 2026
b1b5af6
more bug fixing
Feb 10, 2026
ad99964
link fixes
Feb 10, 2026
6d7d64f
Potential fix for code scanning alert no. 168: Type confusion through…
grahamegrieve Feb 10, 2026
91cc0db
Potential fix for code scanning alert no. 169: Type confusion through…
grahamegrieve Feb 10, 2026
1fd8376
Merge remote-tracking branch 'origin/2026-02-gg-bug-fix-sweep'
grahamegrieve Feb 10, 2026
c09aa85
update axios package
Feb 10, 2026
c97c267
more _fmt defense
Feb 10, 2026
c3ff70b
resync package
Feb 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 136 additions & 15 deletions library/languages.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const fs = require('fs');
const os = require('os');
const {validateOptionalParameter, Utilities} = require("./utilities");
const {join} = require("node:path");

/**
* Language part types for matching depth
Expand All @@ -22,6 +23,7 @@ class LanguageEntry {
constructor() {
this.code = '';
this.displays = [];
this.translations = new Map(); // language code -> translated display name
}
}

Expand Down Expand Up @@ -497,10 +499,12 @@ class LanguageDefinitions {
/**
* Load definitions from IETF language subtag registry file
*/
static async fromFile(filePath) {
static async fromFiles(filePath) {
const definitions = new LanguageDefinitions();
const content = fs.readFileSync(filePath, 'utf8');
const content = fs.readFileSync(join(filePath, "lang.dat"), 'utf8');
definitions._load(content);
definitions._loadTranslations(join(filePath, "languages.csv"), definitions.languages);
definitions._loadTranslations(join(filePath, "regions.csv"), definitions.regions);
return definitions;
}

Expand Down Expand Up @@ -590,6 +594,88 @@ class LanguageDefinitions {
return [vars, i];
}

/**
* Load translations from a CSV file into an existing map of entries.
* CSV format: code,english,french,german,spanish,arabic,chinese,russian,japanese,swahili
* The language codes for the translation columns:
*/
_loadTranslations(csvPath, targetMap) {
if (!fs.existsSync(csvPath)) {
return;
}
const content = fs.readFileSync(csvPath, 'utf8');
const lines = content.split('\n');
if (lines.length < 2) return;

const header = this._parseCsvLine(lines[0]);
// header[0] = 'code', header[1..] = language names mapped to codes
const langCodeMap = {
'english': 'en',
'french': 'fr',
'german': 'de',
'spanish': 'es',
'arabic': 'ar',
'chinese': 'zh',
'russian': 'ru',
'japanese': 'ja',
'swahili': 'sw'
};

const columnLangs = header.slice(1).map(h => langCodeMap[h.toLowerCase()] || h.toLowerCase());

for (let i = 1; i < lines.length; i++) {
const line = lines[i].trim();
if (!line) continue;
const fields = this._parseCsvLine(line);
if (fields.length < 2) continue;

const code = fields[0];
const entry = targetMap.get(code);
if (!entry) continue;

for (let j = 1; j < fields.length && j < header.length; j++) {
const langCode = columnLangs[j - 1];
const value = fields[j];
if (value) {
entry.translations.set(langCode, value);
}
}
}
}

/**
* Parse a single CSV line, handling quoted fields with commas
*/
_parseCsvLine(line) {
const fields = [];
let current = '';
let inQuotes = false;
for (let i = 0; i < line.length; i++) {
const ch = line[i];
if (inQuotes) {
if (ch === '"' && i + 1 < line.length && line[i + 1] === '"') {
current += '"';
i++;
} else if (ch === '"') {
inQuotes = false;
} else {
current += ch;
}
} else {
if (ch === '"') {
inQuotes = true;
} else if (ch === ',') {
fields.push(current);
current = '';
} else {
current += ch;
}
}
}
fields.push(current);
return fields;
}

/**
* Load language entry
*/
Expand Down Expand Up @@ -699,13 +785,29 @@ class LanguageDefinitions {
}

/**
* Get display name for language
* Get display name for language, optionally translated
*/
getDisplayForLang(code, displayIndex = 0) {
const lang = this.languages.get(code);
return lang && lang.displays[displayIndex] ? lang.displays[displayIndex] : code;
}

/**
* Get translated display name for a language code
* @param {string} code - the language subtag
* @param {string} displayLang - the language to translate into (e.g. 'fr', 'de')
* @returns {string} translated name, or English name, or the code itself
*/
getTranslatedDisplayForLang(code, displayLang) {
const lang = this.languages.get(code);
if (!lang) return code;
if (displayLang) {
const translated = lang.translations.get(displayLang);
if (translated) return translated;
}
return lang.displays[0] || code;
}

/**
* Get display name for region
*/
Expand All @@ -714,6 +816,22 @@ class LanguageDefinitions {
return region && region.displays[displayIndex] ? region.displays[displayIndex] : code;
}

/**
* Get translated display name for a region code
* @param {string} code - the region subtag
* @param {string} displayLang - the language to translate into (e.g. 'fr', 'de')
* @returns {string} translated name, or English name, or the code itself
*/
getTranslatedDisplayForRegion(code, displayLang) {
const region = this.regions.get(code);
if (!region) return code;
if (displayLang) {
const translated = region.translations.get(displayLang);
if (translated) return translated;
}
return region.displays[0] || code;
}

/**
* Get display name for script
*/
Expand All @@ -736,18 +854,21 @@ class LanguageDefinitions {
}

let result = this.getDisplayForLang(lang.language, displayIndex);

const parts = [];
if (lang.script) {
parts.push(`Script=${this.getDisplayForScript(lang.script, 0)}`);
}
if (lang.region) {
parts.push(`Region=${this.getDisplayForRegion(lang.region, 0)}`);
}
if (lang.variant) {
const variant = this.variants.get(lang.variant);
const variantDisplay = variant && variant.displays[0] ? variant.displays[0] : lang.variant;
parts.push(`Variant=${variantDisplay}`);
if (lang.region && !lang.script && !lang.variant) {
parts.push(`${this.getDisplayForRegion(lang.region, 0)}`);
} else {
if (lang.script) {
parts.push(`Script=${this.getDisplayForScript(lang.script, 0)}`);
}
if (lang.region) {
parts.push(`Region=${this.getDisplayForRegion(lang.region, 0)}`);
}
if (lang.variant) {
const variant = this.variants.get(lang.variant);
const variantDisplay = variant && variant.displays[0] ? variant.displays[0] : lang.variant;
parts.push(`Variant=${variantDisplay}`);
}
}

if (parts.length > 0) {
Expand Down Expand Up @@ -778,4 +899,4 @@ module.exports = {
LanguageScript,
LanguageRegion,
LanguageVariant
};
};
Loading
Loading