From 9596ae1c1a31298e97a97f78820d32c714501b4a Mon Sep 17 00:00:00 2001 From: rschneider <97682836+rainer-exxcellent@users.noreply.github.com> Date: Thu, 10 Jul 2025 07:36:21 +0200 Subject: [PATCH 1/8] feat(CSAF2.1): #403 add mandatory test 6.2.39.2 --- README.md | 1 + csaf_2_1/recommendedTests.js | 1 + .../recommendedTest_6_2_39_2.js | 153 ++++++++++++++++++ .../translations.js | 17 ++ tests/csaf_2_1/oasis.js | 1 - tests/csaf_2_1/recommendedTest_6_2_39_2.js | 39 +++++ 6 files changed, 211 insertions(+), 1 deletion(-) create mode 100644 csaf_2_1/recommendedTests/recommendedTest_6_2_39_2.js create mode 100644 lib/language_specific_translation/translations.js create mode 100644 tests/csaf_2_1/recommendedTest_6_2_39_2.js diff --git a/README.md b/README.md index 7b581815..86035b8a 100644 --- a/README.md +++ b/README.md @@ -460,6 +460,7 @@ export const recommendedTest_6_2_27: DocumentTest export const recommendedTest_6_2_28: DocumentTest export const recommendedTest_6_2_29: DocumentTest export const recommendedTest_6_2_30: DocumentTest +export const recommendedTest_6_2_39_2: DocumentTest export const recommendedTest_6_2_43: DocumentTest ``` diff --git a/csaf_2_1/recommendedTests.js b/csaf_2_1/recommendedTests.js index d90a5174..373a4649 100644 --- a/csaf_2_1/recommendedTests.js +++ b/csaf_2_1/recommendedTests.js @@ -34,4 +34,5 @@ export { recommendedTest_6_2_28 } from './recommendedTests/recommendedTest_6_2_2 export { recommendedTest_6_2_29 } from './recommendedTests/recommendedTest_6_2_29.js' export { recommendedTest_6_2_30 } from './recommendedTests/recommendedTest_6_2_30.js' export { recommendedTest_6_2_38 } from './recommendedTests/recommendedTest_6_2_38.js' +export { recommendedTest_6_2_39_2 } from './recommendedTests/recommendedTest_6_2_39_2.js' export { recommendedTest_6_2_43 } from './recommendedTests/recommendedTest_6_2_43.js' diff --git a/csaf_2_1/recommendedTests/recommendedTest_6_2_39_2.js b/csaf_2_1/recommendedTests/recommendedTest_6_2_39_2.js new file mode 100644 index 00000000..d471d861 --- /dev/null +++ b/csaf_2_1/recommendedTests/recommendedTest_6_2_39_2.js @@ -0,0 +1,153 @@ +import Ajv from 'ajv/dist/jtd.js' +import translations from '../../lib/language_specific_translation/translations.js' +import bcp47 from 'bcp47' + +const ajv = new Ajv() + +/* + This is the jtd schema that needs to match the input document so that the + test is activated. If this schema doesn't match it normally means that the input + document does not validate against the csaf json schema or optional fields that + the test checks are not present. + */ +const inputSchema = /** @type {const} */ ({ + additionalProperties: true, + properties: { + document: { + additionalProperties: true, + properties: { + category: { type: 'string' }, + }, + optionalProperties: { + lang: { + type: 'string', + }, + notes: { + elements: { + additionalProperties: true, + optionalProperties: { + category: { + type: 'string', + }, + title: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, +}) + +const validateSchema = ajv.compile(inputSchema) + +/** + * Checks if the document language is specified and not English + * + * @param {string | undefined} language - The language expression to check + * @returns {boolean} True if the language is valid, false otherwise + */ +export function isLangSpecifiedAndNotEnglish(language) { + return ( + !!language && !(bcp47.parse(language)?.langtag.language.language === 'en') + ) +} + +/** + * test whether exactly one item in document notes exists that has the given title. + * and the given category. + * @param {({} & { category?: string | undefined; title?: string | undefined; } & Record)[]} notes + * @param {string} titleToFind + * @param {string} category + * @returns {boolean} True if the language is valid, false otherwise + */ +function containsOneNoteWithTitleAndCategory(notes, titleToFind, category) { + return ( + notes.filter( + (note) => note.category === category && note.title === titleToFind + ).length === 1 + ) +} + +/** + * Get the language specific translation of the given i18nKey + * @param {{ document: { lang?: string; }; }} doc + * @param {string} i18nKey + * @return {string | undefined} + */ +export function getTranslationInDocumentLang(doc, i18nKey) { + if (!doc.document.lang) { + return undefined + } + const language = bcp47.parse(doc.document.lang)?.langtag.language.language + + /** @type {Record>}*/ + const translationByLang = translations.translation + if (!language || !translationByLang[language]) { + return undefined + } else { + return translationByLang[language][i18nKey] + } +} + +/** + * If the document language is specified but not English, and the license_expression contains license + * identifiers or exceptions that are not listed in the SPDX license list or Aboutcode's "ScanCode LicenseDB", + * it MUST be tested that exactly one item in document notes exists that has the language specific translation + * of the term License as title. The category of this item MUST be legal_disclaimer. + * If no language-specific translation has been recorded, the test MUST be skipped + * and output information to the user that no such translation is known. + * + * @param {unknown} doc + */ +export function recommendedTest_6_2_39_2(doc) { + /* + The `ctx` variable holds the state that is accumulated during the test run and is + finally returned by the function. + */ + const ctx = { + warnings: + /** @type {Array<{ instancePath: string; message: string }>} */ ([]), + } + + const noteCategory = 'description' + + if (!validateSchema(doc) || doc.document.category !== 'csaf_withdrawn') { + return ctx + } + + const withdrawalInDocLang = getTranslationInDocumentLang( + doc, + 'reasoning_for_withdrawal' + ) + if (!withdrawalInDocLang) { + ctx.warnings.push({ + instancePath: '/document/notes', + message: + 'no language specific translation for "Reasoning for Withdrawal" has been recorded', + }) + return ctx + } + + if (isLangSpecifiedAndNotEnglish(doc.document.lang)) { + const notes = doc.document.notes + if ( + !notes || + !containsOneNoteWithTitleAndCategory( + notes, + withdrawalInDocLang, + 'description' + ) + ) { + ctx.warnings.push({ + instancePath: '/document/notes', + message: + `for document category "csaf_withdrawn" exactly one note must exist ` + + `with note category "${noteCategory}" and title "${withdrawalInDocLang}`, + }) + } + } + + return ctx +} diff --git a/lib/language_specific_translation/translations.js b/lib/language_specific_translation/translations.js new file mode 100644 index 00000000..a9446592 --- /dev/null +++ b/lib/language_specific_translation/translations.js @@ -0,0 +1,17 @@ +/** + * JavaScript version of JSON file: csaf_2.1/language_specific_translation/translations.json + */ +export default { + $schema: + 'https://raw.githubusercontent.com/oasis-tcs/csaf/master/csaf_2.1/test/language_specific_translation/translations_json_schema.json', + translation_version: '2.1', + translation: { + de: { + license: 'Lizenz', + product_description: 'Produktbeschreibung', + reasoning_for_supersession: 'Begründung für die Ersetzung', + reasoning_for_withdrawal: 'Begründung für die Zurückziehung', + superseding_document: 'Ersetzendes Dokument', + }, + }, +} diff --git a/tests/csaf_2_1/oasis.js b/tests/csaf_2_1/oasis.js index fe160623..25b20fe2 100644 --- a/tests/csaf_2_1/oasis.js +++ b/tests/csaf_2_1/oasis.js @@ -43,7 +43,6 @@ const excluded = [ '6.2.36', '6.2.37', '6.2.39.1', - '6.2.39.2', '6.2.39.3', '6.2.39.4', '6.2.40', diff --git a/tests/csaf_2_1/recommendedTest_6_2_39_2.js b/tests/csaf_2_1/recommendedTest_6_2_39_2.js new file mode 100644 index 00000000..564e6fd4 --- /dev/null +++ b/tests/csaf_2_1/recommendedTest_6_2_39_2.js @@ -0,0 +1,39 @@ +import { + getTranslationInDocumentLang, + recommendedTest_6_2_39_2, +} from '../../csaf_2_1/recommendedTests/recommendedTest_6_2_39_2.js' +import { expect } from 'chai' +import assert from 'node:assert' + +describe('recommendedTest_6_2_39_2', function () { + it('only runs on relevant documents', function () { + assert.equal(recommendedTest_6_2_39_2({}).warnings.length, 0) + }) + + it('only runs on valid language', function () { + assert.equal( + recommendedTest_6_2_39_2({ + document: { lang: '123', license_expression: 'MIT' }, + }).warnings.length, + 0 + ) + }) + + it('check get ReasoningForWithdrawal in document lang', function () { + expect( + getTranslationInDocumentLang( + { document: { lang: 'de' } }, + 'reasoning_for_withdrawal' + ) + ).to.eq('Begründung für die Zurückziehung') + expect( + getTranslationInDocumentLang( + { document: { lang: 'es' } }, + 'reasoning_for_withdrawal' + ) + ).to.eq(undefined) + expect( + getTranslationInDocumentLang({ document: {} }, 'reasoning_for_withdrawal') + ).to.eq(undefined) + }) +}) From e38cd73883371ffc0428e8bf1d71324dddc43b68 Mon Sep 17 00:00:00 2001 From: rschneider <97682836+rainer-exxcellent@users.noreply.github.com> Date: Mon, 14 Jul 2025 13:11:05 +0200 Subject: [PATCH 2/8] feat(CSAF2.1): #401 add mandatory test 6.2.39.2 - move common functions to an own file --- .../recommendedTest_6_2_39_2.js | 56 ++----------------- lib/shared/languageSpecificTranslation.js | 54 ++++++++++++++++++ tests/csaf_2_1/recommendedTest_6_2_39_2.js | 6 +- 3 files changed, 61 insertions(+), 55 deletions(-) create mode 100644 lib/shared/languageSpecificTranslation.js diff --git a/csaf_2_1/recommendedTests/recommendedTest_6_2_39_2.js b/csaf_2_1/recommendedTests/recommendedTest_6_2_39_2.js index d471d861..73d3f79f 100644 --- a/csaf_2_1/recommendedTests/recommendedTest_6_2_39_2.js +++ b/csaf_2_1/recommendedTests/recommendedTest_6_2_39_2.js @@ -1,6 +1,9 @@ import Ajv from 'ajv/dist/jtd.js' -import translations from '../../lib/language_specific_translation/translations.js' -import bcp47 from 'bcp47' +import { + containsOneNoteWithTitleAndCategory, + getTranslationInDocumentLang, + isLangSpecifiedAndNotEnglish, +} from '../../lib/shared/languageSpecificTranslation.js' const ajv = new Ajv() @@ -42,55 +45,6 @@ const inputSchema = /** @type {const} */ ({ const validateSchema = ajv.compile(inputSchema) -/** - * Checks if the document language is specified and not English - * - * @param {string | undefined} language - The language expression to check - * @returns {boolean} True if the language is valid, false otherwise - */ -export function isLangSpecifiedAndNotEnglish(language) { - return ( - !!language && !(bcp47.parse(language)?.langtag.language.language === 'en') - ) -} - -/** - * test whether exactly one item in document notes exists that has the given title. - * and the given category. - * @param {({} & { category?: string | undefined; title?: string | undefined; } & Record)[]} notes - * @param {string} titleToFind - * @param {string} category - * @returns {boolean} True if the language is valid, false otherwise - */ -function containsOneNoteWithTitleAndCategory(notes, titleToFind, category) { - return ( - notes.filter( - (note) => note.category === category && note.title === titleToFind - ).length === 1 - ) -} - -/** - * Get the language specific translation of the given i18nKey - * @param {{ document: { lang?: string; }; }} doc - * @param {string} i18nKey - * @return {string | undefined} - */ -export function getTranslationInDocumentLang(doc, i18nKey) { - if (!doc.document.lang) { - return undefined - } - const language = bcp47.parse(doc.document.lang)?.langtag.language.language - - /** @type {Record>}*/ - const translationByLang = translations.translation - if (!language || !translationByLang[language]) { - return undefined - } else { - return translationByLang[language][i18nKey] - } -} - /** * If the document language is specified but not English, and the license_expression contains license * identifiers or exceptions that are not listed in the SPDX license list or Aboutcode's "ScanCode LicenseDB", diff --git a/lib/shared/languageSpecificTranslation.js b/lib/shared/languageSpecificTranslation.js new file mode 100644 index 00000000..6082c04c --- /dev/null +++ b/lib/shared/languageSpecificTranslation.js @@ -0,0 +1,54 @@ +/** + * Checks if the document language is specified and not English + * + * @param {string | undefined} language - The language expression to check + * @returns {boolean} True if the language is valid, false otherwise + */ +export function isLangSpecifiedAndNotEnglish(language) { + return ( + !!language && !(bcp47.parse(language)?.langtag.language.language === 'en') + ) +} +import bcp47 from 'bcp47' +import translations from '../../lib/language_specific_translation/translations.js' + +/** + * test whether exactly one item in document notes exists that has the given title. + * and the given category. + * @param {({} & { category?: string | undefined; title?: string | undefined; } & Record)[]} notes + * @param {string} titleToFind + * @param {string} category + * @returns {boolean} True if the language is valid, false otherwise + */ +export function containsOneNoteWithTitleAndCategory( + notes, + titleToFind, + category +) { + return ( + notes.filter( + (note) => note.category === category && note.title === titleToFind + ).length === 1 + ) +} + +/** + * Get the language specific translation of the given i18nKey + * @param {{ document: { lang?: string; }; }} doc + * @param {string} i18nKey + * @return {string | undefined} + */ +export function getTranslationInDocumentLang(doc, i18nKey) { + if (!doc.document.lang) { + return undefined + } + const language = bcp47.parse(doc.document.lang)?.langtag.language.language + + /** @type {Record>}*/ + const translationByLang = translations.translation + if (!language || !translationByLang[language]) { + return undefined + } else { + return translationByLang[language][i18nKey] + } +} diff --git a/tests/csaf_2_1/recommendedTest_6_2_39_2.js b/tests/csaf_2_1/recommendedTest_6_2_39_2.js index 564e6fd4..52c54fc2 100644 --- a/tests/csaf_2_1/recommendedTest_6_2_39_2.js +++ b/tests/csaf_2_1/recommendedTest_6_2_39_2.js @@ -1,9 +1,7 @@ -import { - getTranslationInDocumentLang, - recommendedTest_6_2_39_2, -} from '../../csaf_2_1/recommendedTests/recommendedTest_6_2_39_2.js' +import { recommendedTest_6_2_39_2 } from '../../csaf_2_1/recommendedTests/recommendedTest_6_2_39_2.js' import { expect } from 'chai' import assert from 'node:assert' +import { getTranslationInDocumentLang } from '../../lib/shared/languageSpecificTranslation.js' describe('recommendedTest_6_2_39_2', function () { it('only runs on relevant documents', function () { From f5bace5e89c06c0072619cb173f70efb40b60318 Mon Sep 17 00:00:00 2001 From: rschneider <97682836+rainer-exxcellent@users.noreply.github.com> Date: Mon, 14 Jul 2025 13:41:12 +0200 Subject: [PATCH 3/8] feat(CSAF2.1): #401 add mandatory test 6.2.39.2 - changed comment --- csaf_2_1/recommendedTests/recommendedTest_6_2_39_2.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/csaf_2_1/recommendedTests/recommendedTest_6_2_39_2.js b/csaf_2_1/recommendedTests/recommendedTest_6_2_39_2.js index 73d3f79f..d861ccde 100644 --- a/csaf_2_1/recommendedTests/recommendedTest_6_2_39_2.js +++ b/csaf_2_1/recommendedTests/recommendedTest_6_2_39_2.js @@ -46,12 +46,10 @@ const inputSchema = /** @type {const} */ ({ const validateSchema = ajv.compile(inputSchema) /** - * If the document language is specified but not English, and the license_expression contains license - * identifiers or exceptions that are not listed in the SPDX license list or Aboutcode's "ScanCode LicenseDB", - * it MUST be tested that exactly one item in document notes exists that has the language specific translation - * of the term License as title. The category of this item MUST be legal_disclaimer. - * If no language-specific translation has been recorded, the test MUST be skipped - * and output information to the user that no such translation is known. + * If the document language is specified but not English, it MUST be tested that exactly one item in document + * notes exists that has the language specific translation of the term Reasoning for Withdrawal as title. + * The category of this item MUST be description. If no language-specific translation has been recorded, + * the test MUST be skipped and output an information to the user that no such translation is known. * * @param {unknown} doc */ From 2611bd42f8be9f1f3b085e5d4114a46f5508640e Mon Sep 17 00:00:00 2001 From: rschneider <97682836+rainer-exxcellent@users.noreply.github.com> Date: Mon, 14 Jul 2025 15:02:02 +0200 Subject: [PATCH 4/8] feat(CSAF2.1): #401 add mandatory test 6.2.39.2 - use category constant --- csaf_2_1/recommendedTests/recommendedTest_6_2_39_2.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/csaf_2_1/recommendedTests/recommendedTest_6_2_39_2.js b/csaf_2_1/recommendedTests/recommendedTest_6_2_39_2.js index d861ccde..11f4f2f5 100644 --- a/csaf_2_1/recommendedTests/recommendedTest_6_2_39_2.js +++ b/csaf_2_1/recommendedTests/recommendedTest_6_2_39_2.js @@ -89,7 +89,7 @@ export function recommendedTest_6_2_39_2(doc) { !containsOneNoteWithTitleAndCategory( notes, withdrawalInDocLang, - 'description' + noteCategory ) ) { ctx.warnings.push({ From 50398f06185b6da6be89592d1a9d5462b3a29fda Mon Sep 17 00:00:00 2001 From: rschneider <97682836+rainer-exxcellent@users.noreply.github.com> Date: Mon, 18 Aug 2025 07:06:16 +0200 Subject: [PATCH 5/8] feat(CSAF2.1): #403 add recommended test 6.2.39.2 - add info on no language specific translation to ctx.infos --- csaf_2_1/recommendedTests/recommendedTest_6_2_39_2.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/csaf_2_1/recommendedTests/recommendedTest_6_2_39_2.js b/csaf_2_1/recommendedTests/recommendedTest_6_2_39_2.js index 11f4f2f5..65125ae9 100644 --- a/csaf_2_1/recommendedTests/recommendedTest_6_2_39_2.js +++ b/csaf_2_1/recommendedTests/recommendedTest_6_2_39_2.js @@ -58,9 +58,11 @@ export function recommendedTest_6_2_39_2(doc) { The `ctx` variable holds the state that is accumulated during the test run and is finally returned by the function. */ + /** @type { {warnings: Array<{ message: string; instancePath: string }>; + * infos: Array<{ message: string; instancePath: string }>}} */ const ctx = { - warnings: - /** @type {Array<{ instancePath: string; message: string }>} */ ([]), + warnings: [], + infos: [], } const noteCategory = 'description' @@ -74,7 +76,7 @@ export function recommendedTest_6_2_39_2(doc) { 'reasoning_for_withdrawal' ) if (!withdrawalInDocLang) { - ctx.warnings.push({ + ctx.infos.push({ instancePath: '/document/notes', message: 'no language specific translation for "Reasoning for Withdrawal" has been recorded', From 5f7e5410af45a70b8f5378271f3b9c3443876366 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Burgd=C3=B6rfer?= Date: Tue, 16 Sep 2025 15:50:36 +0200 Subject: [PATCH 6/8] refactor translation utils --- lib/shared/languageSpecificTranslation.js | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/lib/shared/languageSpecificTranslation.js b/lib/shared/languageSpecificTranslation.js index 6082c04c..8663bfe9 100644 --- a/lib/shared/languageSpecificTranslation.js +++ b/lib/shared/languageSpecificTranslation.js @@ -1,3 +1,13 @@ +import bcp47 from 'bcp47' +import translations from '../../lib/language_specific_translation/translations.js' + +const translationMap = new Map( + Object.entries(translations.translation).map(([key, value]) => [ + key, + new Map(Object.entries(value)), + ]) +) + /** * Checks if the document language is specified and not English * @@ -9,13 +19,11 @@ export function isLangSpecifiedAndNotEnglish(language) { !!language && !(bcp47.parse(language)?.langtag.language.language === 'en') ) } -import bcp47 from 'bcp47' -import translations from '../../lib/language_specific_translation/translations.js' /** * test whether exactly one item in document notes exists that has the given title. * and the given category. - * @param {({} & { category?: string | undefined; title?: string | undefined; } & Record)[]} notes + * @param {Array<{ category?: string | undefined; title?: string | undefined; }>} notes * @param {string} titleToFind * @param {string} category * @returns {boolean} True if the language is valid, false otherwise @@ -36,19 +44,14 @@ export function containsOneNoteWithTitleAndCategory( * Get the language specific translation of the given i18nKey * @param {{ document: { lang?: string; }; }} doc * @param {string} i18nKey - * @return {string | undefined} */ export function getTranslationInDocumentLang(doc, i18nKey) { if (!doc.document.lang) { return undefined } const language = bcp47.parse(doc.document.lang)?.langtag.language.language - - /** @type {Record>}*/ - const translationByLang = translations.translation - if (!language || !translationByLang[language]) { + if (!language) { return undefined - } else { - return translationByLang[language][i18nKey] } + return translationMap.get(language)?.get(i18nKey) } From 175b0f64fb214ca1d936d7f0a71c8bee39fee491 Mon Sep 17 00:00:00 2001 From: rschneider <97682836+rainer-exxcellent@users.noreply.github.com> Date: Tue, 21 Oct 2025 10:48:09 +0200 Subject: [PATCH 7/8] feat(CSAF2.1): #403 add recommended test 6.2.39.2 - improve tests and comments in language lib --- lib/shared/languageSpecificTranslation.js | 2 +- tests/csaf_2_1/recommendedTest_6_2_39_2.js | 26 ++++++++++++++++------ 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/lib/shared/languageSpecificTranslation.js b/lib/shared/languageSpecificTranslation.js index 8663bfe9..98579763 100644 --- a/lib/shared/languageSpecificTranslation.js +++ b/lib/shared/languageSpecificTranslation.js @@ -12,7 +12,7 @@ const translationMap = new Map( * Checks if the document language is specified and not English * * @param {string | undefined} language - The language expression to check - * @returns {boolean} True if the language is valid, false otherwise + * @returns {boolean} False if language is English, true if the language is valid, false otherwise */ export function isLangSpecifiedAndNotEnglish(language) { return ( diff --git a/tests/csaf_2_1/recommendedTest_6_2_39_2.js b/tests/csaf_2_1/recommendedTest_6_2_39_2.js index 52c54fc2..9f9735ef 100644 --- a/tests/csaf_2_1/recommendedTest_6_2_39_2.js +++ b/tests/csaf_2_1/recommendedTest_6_2_39_2.js @@ -8,13 +8,25 @@ describe('recommendedTest_6_2_39_2', function () { assert.equal(recommendedTest_6_2_39_2({}).warnings.length, 0) }) - it('only runs on valid language', function () { - assert.equal( - recommendedTest_6_2_39_2({ - document: { lang: '123', license_expression: 'MIT' }, - }).warnings.length, - 0 - ) + it('only runs on valid category', function () { + const result = recommendedTest_6_2_39_2({ + document: { category: '123', license_expression: 'MIT' }, + }) + + assert.equal(result.warnings.length, 0) + assert.equal(result.infos.length, 0) + }) + + it('info on invalid language', function () { + const result = recommendedTest_6_2_39_2({ + document: { + category: 'csaf_withdrawn', + lang: '123', + license_expression: 'MIT', + }, + }) + assert.equal(result.warnings.length, 0) + assert.equal(result.infos.length, 1) }) it('check get ReasoningForWithdrawal in document lang', function () { From 07b1b7914c9d76b158c12b5f8429ae5c9d23b416 Mon Sep 17 00:00:00 2001 From: rschneider <97682836+rainer-exxcellent@users.noreply.github.com> Date: Tue, 28 Oct 2025 07:33:24 +0100 Subject: [PATCH 8/8] feat(CSAF2.1): #403 add recommended test 6.2.39.2 - add regions and script code to translation --- lib/shared/languageSpecificTranslation.js | 38 +++++++++++++++++----- tests/languageSpecificTranslation.js | 39 +++++++++++++++++++++++ 2 files changed, 69 insertions(+), 8 deletions(-) create mode 100644 tests/languageSpecificTranslation.js diff --git a/lib/shared/languageSpecificTranslation.js b/lib/shared/languageSpecificTranslation.js index 98579763..a4e301b1 100644 --- a/lib/shared/languageSpecificTranslation.js +++ b/lib/shared/languageSpecificTranslation.js @@ -1,7 +1,7 @@ import bcp47 from 'bcp47' import translations from '../../lib/language_specific_translation/translations.js' -const translationMap = new Map( +const csafTranslationMaps = new Map( Object.entries(translations.translation).map(([key, value]) => [ key, new Map(Object.entries(value)), @@ -46,12 +46,34 @@ export function containsOneNoteWithTitleAndCategory( * @param {string} i18nKey */ export function getTranslationInDocumentLang(doc, i18nKey) { - if (!doc.document.lang) { - return undefined - } - const language = bcp47.parse(doc.document.lang)?.langtag.language.language - if (!language) { - return undefined + return doc.document.lang + ? getTranslationInMap(doc.document.lang, i18nKey, csafTranslationMaps) + : undefined +} + +/** + * Get the language specific translation of the given i18nKey in translationMaps + * @param {string} langToTranslate + * @param {string} i18nKey + * @param {Map>} translationMaps + */ +export function getTranslationInMap(langToTranslate, i18nKey, translationMaps) { + const langtag = bcp47.parse(langToTranslate)?.langtag + const languageCode = langtag?.language.language + let transMapForLanguage + if (langtag && languageCode) { + if (langtag.region) { + if (langtag.script) { + transMapForLanguage = translationMaps.get( + `${languageCode}-${langtag.script}-${langtag.region}` + ) + } + transMapForLanguage = + transMapForLanguage ?? + translationMaps.get(`${languageCode}-${langtag.region}`) + } + transMapForLanguage = + transMapForLanguage ?? translationMaps.get(languageCode) } - return translationMap.get(language)?.get(i18nKey) + return transMapForLanguage?.get(i18nKey) } diff --git a/tests/languageSpecificTranslation.js b/tests/languageSpecificTranslation.js new file mode 100644 index 00000000..83154a2c --- /dev/null +++ b/tests/languageSpecificTranslation.js @@ -0,0 +1,39 @@ +import { getTranslationInMap } from '../lib/shared/languageSpecificTranslation.js' +import { expect } from 'chai' + +describe('test language specific translation', function () { + it('test getTranslationInMap', function () { + const translationMaps = new Map([ + ['de', new Map([['I18nTestKey', 'translationDe']])], + ['de-AT', new Map([['I18nTestKey', 'translationAT']])], + ['en', new Map([['I18nTestKey', 'translationEn']])], + ['zh-Hans-CN', new Map([['I18nTestKey', 'translationZh-Hans-CN']])], + ['sr', new Map([['I18nTestKey', 'translationSr']])], + ]) + + expect( + getTranslationInMap('de', 'I18nTestKey', translationMaps), + 'Translate language code de' + ).to.equal('translationDe') + expect( + getTranslationInMap('de-AT', 'I18nTestKey', translationMaps), + 'Translate language and region code' + ).to.equal('translationAT') + expect( + getTranslationInMap('en', 'I18nTestKey', translationMaps), + 'Translate language code en' + ).to.equal('translationEn') + expect( + getTranslationInMap('en-US', 'I18nTestKey', translationMaps), + 'Fallback to language code en on region us' + ).to.equal('translationEn') + expect( + getTranslationInMap('zh-Hans-CN', 'I18nTestKey', translationMaps), + 'Translate language, region and script code' + ).to.equal('translationZh-Hans-CN') + expect( + getTranslationInMap('sr-Cyrl-RS', 'I18nTestKey', translationMaps), + 'Fallback to language code en on region an sript code' + ).to.equal('translationSr') + }) +})