From 722b133edb413566e0947fdcdaf619eed95b5f12 Mon Sep 17 00:00:00 2001 From: Tim van der Lippe Date: Wed, 13 May 2026 11:27:21 +0200 Subject: [PATCH 01/10] Voeg regel toe voor informatie over talen Hiermee standaardiseren we het gebruik van talen, welk formaat taal codes in geschreven moeten worden en welke structuur in een object moet worden gebruikt. --- sections/designRules.md | 83 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/sections/designRules.md b/sections/designRules.md index abddf71..7bd0ab0 100644 --- a/sections/designRules.md +++ b/sections/designRules.md @@ -214,6 +214,89 @@ https://api.example.org/v1/vergunningen/d285e05c-6b01-45c3-92d8-5e19a946b66f +
+

Use standard language codes for localization

+
+
Statement
+
+

A resource containing localized information MUST follow BCP 47 [[RFC4647]] [[RFC5646]]. + All fields in requests and responses containing singular localized information MUST be an object with a field "taal" (Dutch) or "language" (English) with a value conforming [[RFC5646]] and a field "waarde" (Dutch) or "value" (English) with the localized string. +

Use the language subtag registry maintained by IANA for possible language subtags. +

+ +

All fields in requests and responses containing multiple localized options MUST be an array of objects where all objects have a field "taal" (Dutch) or all have a field "language" (English) with a value conforming [[RFC5646]] and all have a field "waarde" (Dutch) or have a field "value" (English) with the localized string. +

+

+ +

[[?ISO3166-1]] concerns identifiers of countries and MUST NOT be used to denote languages, since countries and languages are not equivalent. +

Following [[RFC4647]] a language code in [[?ISO-639-1]] format match a language tag in [[RFC5646]] regardless of language subtag. +

+
Rationale
+
+ Standardized language codes removes ambiguity in language handling between systems, potentially present in separate regions with different (spoken) languages. +
+
How to test
+
+
    +
  • Analyse all fields and if the field represents a language and ensure either one of the following options applies: +
      +
    • In case of singular localized information, ensure it is an object. It has two fields "taal" and "waarde" or it has two fields "language" and "value". +
    • In case of multiple localized options, ensure it is an array. Either all objects have two fields "taal" and "waarde" or all objects have two fields "language" and "value". +
    +
  • Confirm each field "taal" or "language" has a value in [[RFC5646]] format. +
+
+
+
+ ## Date and time Handling date and time is tricky and can lead to confusion among clients. The date-time rules remove ambiguity and provide clarity in the API contract between servers and clients. From d23839b882a39d545c64803fbad852a272799a45 Mon Sep 17 00:00:00 2001 From: Tim van der Lippe Date: Wed, 13 May 2026 11:39:43 +0200 Subject: [PATCH 02/10] Fix build --- sections/designRules.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sections/designRules.md b/sections/designRules.md index 7bd0ab0..e1cc14a 100644 --- a/sections/designRules.md +++ b/sections/designRules.md @@ -243,7 +243,6 @@ https://api.example.org/v1/vergunningen/d285e05c-6b01-45c3-92d8-5e19a946b66f

All fields in requests and responses containing multiple localized options MUST be an array of objects where all objects have a field "taal" (Dutch) or all have a field "language" (English) with a value conforming [[RFC5646]] and all have a field "waarde" (Dutch) or have a field "value" (English) with the localized string. -

[[?ISO3166-1]] concerns identifiers of countries and MUST NOT be used to denote languages, since countries and languages are not equivalent. -

Following [[RFC4647]] a language code in [[?ISO-639-1]] format match a language tag in [[RFC5646]] regardless of language subtag. +

Following [[RFC4647]] a language code in [[?ISO-639-1]] format matches a language tag in [[RFC5646]] regardless of language subtag.

Rationale
From 7f5bcc9f970ba1d304526a6a494aafed8e970c5c Mon Sep 17 00:00:00 2001 From: Tim van der Lippe Date: Wed, 13 May 2026 11:44:42 +0200 Subject: [PATCH 03/10] Maak test instructie specifieker --- sections/designRules.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sections/designRules.md b/sections/designRules.md index e1cc14a..9ba22c7 100644 --- a/sections/designRules.md +++ b/sections/designRules.md @@ -288,7 +288,7 @@ https://api.example.org/v1/vergunningen/d285e05c-6b01-45c3-92d8-5e19a946b66fAnalyse all fields and if the field represents a language and ensure either one of the following options applies:
  • In case of singular localized information, ensure it is an object. It has two fields "taal" and "waarde" or it has two fields "language" and "value". -
  • In case of multiple localized options, ensure it is an array. Either all objects have two fields "taal" and "waarde" or all objects have two fields "language" and "value". +
  • In case of multiple localized options, ensure it is an array consisting of objects. Either all objects have two fields "taal" and "waarde" or all objects have two fields "language" and "value".
  • Confirm each field "taal" or "language" has a value in [[RFC5646]] format. From d3dfeb43c51d2a771853926bbbd47a79bc37d32e Mon Sep 17 00:00:00 2001 From: Tim van der Lippe Date: Wed, 27 May 2026 13:24:18 +0200 Subject: [PATCH 04/10] Update designRules.md --- sections/designRules.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/sections/designRules.md b/sections/designRules.md index 9ba22c7..a3d736b 100644 --- a/sections/designRules.md +++ b/sections/designRules.md @@ -215,61 +215,61 @@ https://api.example.org/v1/vergunningen/d285e05c-6b01-45c3-92d8-5e19a946b66f
    -

    Use standard language codes for localization

    +

    Use standard language codes and field names for multilingual content

    Statement

    A resource containing localized information MUST follow BCP 47 [[RFC4647]] [[RFC5646]]. - All fields in requests and responses containing singular localized information MUST be an object with a field "taal" (Dutch) or "language" (English) with a value conforming [[RFC5646]] and a field "waarde" (Dutch) or "value" (English) with the localized string. + All fields in requests and responses containing singular localized information MUST be an object with two fields (JSON) or tag with two subtags (XML). In it, "taal" (Dutch) or "language" (English) contains a value conforming [[RFC5646]] and "waarde" (Dutch) or "value" (English) contains the localized string.

    Use the language subtag registry maintained by IANA for possible language subtags.

    All fields in requests and responses containing multiple localized options MUST be an array of objects where all objects have a field "taal" (Dutch) or all have a field "language" (English) with a value conforming [[RFC5646]] and all have a field "waarde" (Dutch) or have a field "value" (English) with the localized string.

    Rationale
    - Standardized language codes removes ambiguity in language handling between systems, potentially present in separate regions with different (spoken) languages. + Standardised language codes removes ambiguity in language handling between systems, potentially present in separate regions with different (spoken) languages.
    How to test
    From b205ecef683c26925f04509e6c6bbee2697909aa Mon Sep 17 00:00:00 2001 From: Tim van der Lippe Date: Wed, 19 Aug 2026 11:06:39 +0200 Subject: [PATCH 08/10] Voeg subtag toe aan dictionary --- standaard.dic | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/standaard.dic b/standaard.dic index eecc42a..69e58e0 100644 --- a/standaard.dic +++ b/standaard.dic @@ -1,4 +1,4 @@ -32 +33 Aanbeveling backend camelCase @@ -25,6 +25,7 @@ SameSite Specref Strategie subresource +subtag URIs voor WebDAV From 531d9053eb1ff9b4dd4df7ea9c0c62d9b035e2b2 Mon Sep 17 00:00:00 2001 From: Tim van der Lippe Date: Wed, 19 Aug 2026 11:24:21 +0200 Subject: [PATCH 09/10] Voeg linter check toe --- .../language-code/expected-output.txt | 17 ++ linter/testcases/language-code/openapi.json | 256 ++++++++++++++++++ media/linter.yaml | 33 +++ 3 files changed, 306 insertions(+) create mode 100644 linter/testcases/language-code/expected-output.txt create mode 100644 linter/testcases/language-code/openapi.json diff --git a/linter/testcases/language-code/expected-output.txt b/linter/testcases/language-code/expected-output.txt new file mode 100644 index 0000000..fcbcea2 --- /dev/null +++ b/linter/testcases/language-code/expected-output.txt @@ -0,0 +1,17 @@ + +/testcases/language-code/openapi.json + 92:52 error nlgov:specify-format-for-language-code Any field that represents a language must set 'format' to 'language' paths./resources-with-language-incorrect.get.responses[200].content.application/json.schema.properties.language + 95:65 error nlgov:specify-format-for-language-code Any field that represents a language must set 'format' to 'language' paths./resources-with-language-incorrect.get.responses[200].content.application/json.schema.properties.thoroughfare_language + 98:70 error nlgov:specify-format-for-language-code Any field that represents a language must set 'format' to 'language' paths./resources-with-language-incorrect.get.responses[200].content.application/json.schema.properties.railName_localNameLanguage + 101:48 error nlgov:specify-format-for-language-code Any field that represents a language must set 'format' to 'language' paths./resources-with-language-incorrect.get.responses[200].content.application/json.schema.properties.taal + 104:59 error nlgov:specify-format-for-language-code Any field that represents a language must set 'format' to 'language' paths./resources-with-language-incorrect.get.responses[200].content.application/json.schema.properties.organisatieTaal + 107:60 error nlgov:specify-format-for-language-code Any field that represents a language must set 'format' to 'language' paths./resources-with-language-incorrect.get.responses[200].content.application/json.schema.properties.organisatie_taal + 127:55 error nlgov:use-language-code Field represents a language and therefore must set 'format' to 'language' paths./resources-with-language-incorrect.get.responses[200].content.application/json+verkeerd-format.schema.properties.language.format + 131:55 error nlgov:use-language-code Field represents a language and therefore must set 'format' to 'language' paths./resources-with-language-incorrect.get.responses[200].content.application/json+verkeerd-format.schema.properties.thoroughfare_language.format + 135:55 error nlgov:use-language-code Field represents a language and therefore must set 'format' to 'language' paths./resources-with-language-incorrect.get.responses[200].content.application/json+verkeerd-format.schema.properties.railName_localNameLanguage.format + 139:55 error nlgov:use-language-code Field represents a language and therefore must set 'format' to 'language' paths./resources-with-language-incorrect.get.responses[200].content.application/json+verkeerd-format.schema.properties.taal.format + 143:55 error nlgov:use-language-code Field represents a language and therefore must set 'format' to 'language' paths./resources-with-language-incorrect.get.responses[200].content.application/json+verkeerd-format.schema.properties.organisatieTaal.format + 147:55 error nlgov:use-language-code Field represents a language and therefore must set 'format' to 'language' paths./resources-with-language-incorrect.get.responses[200].content.application/json+verkeerd-format.schema.properties.organisatie_taal.format + 236:27 error nlgov:use-language-code Field represents a language and therefore must set 'format' to 'language' components.schemas.LanguageIncorrect.format + +✖ 13 problems (13 errors, 0 warnings, 0 infos, 0 hints) diff --git a/linter/testcases/language-code/openapi.json b/linter/testcases/language-code/openapi.json new file mode 100644 index 0000000..38720e7 --- /dev/null +++ b/linter/testcases/language-code/openapi.json @@ -0,0 +1,256 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "Baseline", + "description": "Deze OpenAPI specification bevat het minimale om aan alle regels te voldoen.", + "contact": { + "name": "Beheerder", + "url": "https://www.example.com", + "email": "mail@example.com" + }, + "version": "1.0.0" + }, + "servers": [ + { + "url": "https://example.com/api/v1" + } + ], + "security": [ + { + "default": [] + } + ], + "tags": [ + { + "name": "openapi" + }, + { + "name": "language" + } + ], + "paths": { + "/openapi.json": { + "get": { + "tags": [ + "openapi" + ], + "description": "OpenAPI document", + "operationId": "getOpenapiJSON", + "parameters": [], + "responses": { + "200": { + "description": "OK", + "headers": { + "API-Version": { + "description": "De huidige versie van de applicatie", + "style": "simple", + "schema": { + "type": "string" + } + }, + "access-control-allow-origin": { + "description": "Alle origins mogen bij deze resource", + "schema": { + "type": "string" + } + } + } + } + }, + "security": [ + { + "default": [] + } + ] + } + }, + "/resources-with-language-incorrect": { + "get": { + "tags": [ + "language" + ], + "description": "Resources with language incorrect", + "operationId": "getLanguageIncorrect", + "parameters": [], + "responses": { + "200": { + "description": "OK", + "headers": { + "API-Version": { + "description": "De huidige versie van de applicatie", + "style": "simple", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "language": { + "type": "string" + }, + "thoroughfare_language": { + "type": "string" + }, + "railName_localNameLanguage": { + "type": "string" + }, + "taal": { + "type": "string" + }, + "organisatieTaal": { + "type": "string" + }, + "organisatie_taal": { + "type": "string" + }, + "meerdereTaal": { + "type": "string", + "allOf": [ + { + "$ref": "#/components/schemas/LanguageIncorrect" + } + ] + } + } + } + }, + "application/json+verkeerd-format": { + "schema": { + "type": "object", + "properties": { + "language": { + "type": "string", + "format": "byte" + }, + "thoroughfare_language": { + "type": "string", + "format": "byte" + }, + "railName_localNameLanguage": { + "type": "string", + "format": "byte" + }, + "taal": { + "type": "string", + "format": "byte" + }, + "organisatieTaal": { + "type": "string", + "format": "byte" + }, + "organisatie_taal": { + "type": "string", + "format": "byte" + } + } + } + } + } + } + }, + "security": [ + { + "default": [] + } + ] + } + }, + "/resources-with-language-correct": { + "get": { + "tags": [ + "language" + ], + "description": "Resources with language correct", + "operationId": "getLanguagesCorrect", + "parameters": [], + "responses": { + "200": { + "description": "OK", + "headers": { + "API-Version": { + "description": "De huidige versie van de applicatie", + "style": "simple", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "language": { + "type": "string", + "format": "language" + }, + "thoroughfare_language": { + "type": "string", + "format": "language" + }, + "railName_localNameLanguage": { + "type": "string", + "format": "language" + }, + "taal": { + "type": "string", + "format": "language" + }, + "organisatieTaal": { + "type": "string", + "format": "language" + }, + "organisatie_taal": { + "type": "string", + "format": "language" + }, + "meerdereTaal": { + "type": "string", + "allOf": [ + { + "$ref": "#/components/schemas/LanguageCorrect" + } + ] + } + } + } + } + } + } + }, + "security": [ + { + "default": [] + } + ] + } + } + }, + "components": { + "schemas": { + "LanguageIncorrect": { + "format": "byte", + "type": "string" + }, + "LanguageCorrect": { + "format": "language", + "type": "string" + } + }, + "securitySchemes": { + "default": { + "type": "oauth2", + "flows": { + "implicit": { + "authorizationUrl": "https://test.com", + "scopes": {} + } + } + } + } + } +} \ No newline at end of file diff --git a/media/linter.yaml b/media/linter.yaml index 679a5aa..03d166a 100644 --- a/media/linter.yaml +++ b/media/linter.yaml @@ -282,6 +282,39 @@ rules: functionOptions: notMatch: "/^date-time$/" + #/core/lang-code + nlgov:specify-format-for-language-code: + severity: error + given: + - $..properties[taal,lang,language] + - $..properties[?(@property && @property.match(/((\w+T)|(_[tT]))aal/))] + - $..properties[?(@property && @property.match(/((\w+L)|(_[lL]))ang(uage)?/))] + message: "Any field that represents a language must set 'format' to 'language'" + then: + function: schema + functionOptions: + schema: + anyOf: + - required: ["format"] + - properties: + allOf: + type: array + items: + required: ["format"] + required: ["allOf"] + + nlgov:use-language-code: + severity: error + given: + - $..properties[taal,lang,language]..format + - $..properties[?(@property && @property.match(/((\w+T)|(_[tT]))aal/))]..format + - $..properties[?(@property && @property.match(/((\w+L)|(_[lL]))ang(uage)?/))]..format + message: "Field represents a language and therefore must set 'format' to 'language'" + then: + function: pattern + functionOptions: + match: "/^language$/" + nlgov:semver: severity: error message: "Version {{value}} is not in semver format." From 20de14555e93a7d4884b213fb0eb0bd7abe8373a Mon Sep 17 00:00:00 2001 From: Tim van der Lippe Date: Tue, 1 Sep 2026 13:45:40 +0200 Subject: [PATCH 10/10] Update designRules.md --- sections/designRules.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sections/designRules.md b/sections/designRules.md index 0ec997a..76fab69 100644 --- a/sections/designRules.md +++ b/sections/designRules.md @@ -220,8 +220,16 @@ https://api.example.org/v1/vergunningen/d285e05c-6b01-45c3-92d8-5e19a946b66fStatement

    A resource containing language content MUST follow BCP 47 [[RFC4647]] [[RFC5646]]. + The Primary Language Subtag MUST be lowercase. + Languages MUST have a Region subtag in uppercase.

    [[?ISO3166-1]] concerns identifiers of countries and MUST NOT be used to denote languages, since countries and languages are not equivalent.

    Following [[RFC4647]] a language code in [[?ISO-639-1]] format matches a language tag in [[RFC5646]] format regardless of language subtag. +

    +
    Rationale