diff --git a/.gitlab/ci/docs.gitlab-ci.yml b/.gitlab/ci/docs.gitlab-ci.yml
index 312c57aba586c76058f8e5ff926d370570503d07..3003370e842b05e6356ef8ce49668db32e6bbd1e 100644
--- a/.gitlab/ci/docs.gitlab-ci.yml
+++ b/.gitlab/ci/docs.gitlab-ci.yml
@@ -196,6 +196,24 @@ docs-i18n-lint markdown:
- install_gitlab_gem
- scripts/i18n_lint_doc.sh
+# Japanese-specific Vale linting with language-specific rules
+docs-i18n-lint japanese-vale:
+ extends:
+ - .default-retry
+ - .docs:rules:docs-i18n-lint-japanese
+ - .docs-markdown-lint-image
+ stage: lint
+ needs: []
+ variables:
+ LANGUAGE_CODE: "ja-jp"
+ LANGUAGE_NAME: "Japanese"
+ VALE_FILTER: "locale_rules"
+ VALE_MIN_ALERT_LEVEL: "warning"
+ script:
+ - source ./scripts/utils.sh
+ - install_gitlab_gem
+ - scripts/i18n_lint_language_vale.sh
+
# Verify localized documentation files have corresponding English versions
docs-i18n-lint paths:
extends:
diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml
index b13fa9391a41b0de3890a82404ddfec653380401..49436c87d0990979b7859f58624fb50029e1d4f6 100644
--- a/.gitlab/ci/rules.gitlab-ci.yml
+++ b/.gitlab/ci/rules.gitlab-ci.yml
@@ -351,11 +351,17 @@
- "tooling/eslint-config/eslint-local-rules/**/*"
.docs-i18n-patterns: &docs-i18n-patterns
+ - ".gitlab/ci/docs.gitlab-ci.yml"
- "doc-locale/**/*"
- "doc-locale/.markdownlint/.markdownlint-cli2.yaml"
- "scripts/i18n_lint_doc.sh"
- "scripts/i18n_verify_paths.sh"
+.docs-ja-jp-patterns: &docs-ja-jp-patterns
+ - ".gitlab/ci/docs.gitlab-ci.yml"
+ - "scripts/i18n_lint_language_vale.sh"
+ - "doc-locale/ja-jp/**/*"
+
.if-tech-docs-localization: &if-tech-docs-localization
if: '($CI_PROJECT_PATH == "gitlab-com/localization" && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH) ||
($CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_EVENT_TYPE != "merge_train") ||
@@ -1311,6 +1317,11 @@
- <<: *if-tech-docs-localization
changes: *docs-i18n-patterns
+.docs:rules:docs-i18n-lint-japanese:
+ rules:
+ - <<: *if-tech-docs-localization
+ changes: *docs-ja-jp-patterns
+
.docs:rules:deprecations-and-removals:
rules:
- <<: *if-default-refs
diff --git a/doc-locale/ja-jp/.vale.ini b/doc-locale/ja-jp/.vale.ini
new file mode 100644
index 0000000000000000000000000000000000000000..2e46c325ecd392d1dfb860235d052ebf57fb4f3e
--- /dev/null
+++ b/doc-locale/ja-jp/.vale.ini
@@ -0,0 +1,14 @@
+# Vale configuration file for localized documentation.
+#
+# For more information, see https://vale.sh/docs/vale-ini.
+
+StylesPath = .vale
+MinAlertLevel = suggestion
+
+IgnoredScopes = code, text.frontmatter.redirect_to
+
+[*.md]
+BasedOnStyles = locale_rules
+
+# Ignore SVG markup
+TokenIgnores = (\*\*\{\w*\}\*\*)
diff --git a/doc-locale/ja-jp/.vale/locale_rules/ReferenceQuotationRemoval.yml b/doc-locale/ja-jp/.vale/locale_rules/ReferenceQuotationRemoval.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7e992b40d5ddaffaf1e00622a78a2cd013d231db
--- /dev/null
+++ b/doc-locale/ja-jp/.vale/locale_rules/ReferenceQuotationRemoval.yml
@@ -0,0 +1,11 @@
+---
+name: locale_rules.ReferenceQuotationRemoval
+description: |
+ Remove quotation marks around reference links
+extends: existence
+message: "Check for quotation marks around reference links at '%s'"
+level: warning
+nonword: true
+scope: raw
+tokens:
+ - '「\[.+?\]\([^)]+\)」'
diff --git a/doc-locale/ja-jp/.vale/vale.tmpl b/doc-locale/ja-jp/.vale/vale.tmpl
new file mode 100644
index 0000000000000000000000000000000000000000..3fe958023b9f28871c10c8a6a0a7f6947dcb4dea
--- /dev/null
+++ b/doc-locale/ja-jp/.vale/vale.tmpl
@@ -0,0 +1,48 @@
+{{- /* Modify Vale's output https://docs.errata.ai/vale/cli#--output */ -}}
+
+{{- /* Keep track of our various counts */ -}}
+
+{{- $e := 0 -}}
+{{- $w := 0 -}}
+{{- $s := 0 -}}
+
+{{- /* Range over the linted files */ -}}
+
+{{- range .Files}}
+{{- $path := .Path | underline -}}
+
+{{- /* Range over the file's alerts */ -}}
+
+{{- range .Alerts -}}
+
+{{- $error := "" -}}
+{{- if eq .Severity "error" -}}
+ {{- $error = .Severity | red -}}
+ {{- $e = add1 $e -}}
+{{- else if eq .Severity "warning" -}}
+ {{- $error = .Severity | yellow -}}
+ {{- $w = add1 $w -}}
+{{- else -}}
+ {{- $error = .Severity | blue -}}
+ {{- $s = add1 $s -}}
+{{- end}}
+
+{{- /* Variables setup */ -}}
+
+{{- $path = $path -}}
+{{- $loc := printf "Line %d, position %d" .Line (index .Span 0) -}}
+{{- $check := printf "%s" .Check -}}
+{{- $message := printf "%s" .Message -}}
+{{- $link := printf "%s" .Link -}}
+
+{{- /* Output */ -}}
+
+{{ $path }}:
+ {{ $loc }} (rule {{ $check }})
+ {{ $error }}: {{ $message }}
+ More information: {{ $link }}
+
+{{end -}}
+{{end -}}
+
+{{- $e}} {{"errors" | red}}, {{$w}} {{"warnings" | yellow}}, and {{$s}} {{"suggestions" | blue}} found in {{.LintedTotal}} {{.LintedTotal | int | plural "file" "files"}}.
diff --git a/doc/development/documentation/testing/_index.md b/doc/development/documentation/testing/_index.md
index 2cfc017a749f0d15c25e37ab978e76fb8d931aca..809dba457177d8d3098f8b1729100bd07537d88c 100644
--- a/doc/development/documentation/testing/_index.md
+++ b/doc/development/documentation/testing/_index.md
@@ -94,15 +94,94 @@ To ensure quality across all our translated content, we've implemented testing f
multiple languages. These tests mirror those used for the English version, but run on internationalized
content in the `/doc-locale/` or `/docs-locale/` directories.
+### General translation linting
+
+The `docs-i18n-lint markdown` job runs general linting tests on all translated documentation:
+
+- **markdownlint**: Checks Markdown structure and formatting
+- **Vale**: Runs general documentation style rules using the `gitlab_docs` filter
+
+This job runs when any files in `/doc-locale/` or `/docs-locale/` are modified and provides baseline quality checks
+for all translated content.
+
| Project | English Dir | Translation Dir | Linting Jobs |
| ----- | ----- | ----- | ----- |
-| GitLab | [`/doc`](https://gitlab.com/gitlab-org/gitlab/-/tree/master/doc) | [`/doc-locale`](https://gitlab.com/gitlab-org/gitlab/-/tree/master/doc-locale) | `docs-i18n-lint markdown` |
+| GitLab | [`/doc`](https://gitlab.com/gitlab-org/gitlab/-/tree/master/doc) | [`/doc-locale`](https://gitlab.com/gitlab-org/gitlab/-/tree/master/doc-locale) | `docs-i18n-lint markdown`
`docs-i18n-lint japanese-vale` |
| GitLab Runner | [`/docs`](https://gitlab.com/gitlab-org/gitlab-runner/-/tree/main/docs) | [`/docs-locale`](https://gitlab.com/gitlab-org/gitlab-runner/-/tree/main/docs-locale?ref_type=heads) | `docs:lint i18n markdown` |
| Linux package | [`/doc`](https://gitlab.com/gitlab-org/omnibus-gitlab/-/tree/master/doc) | [`/doc-locale`](https://gitlab.com/gitlab-org/omnibus-gitlab/-/tree/master/doc-locale) | `docs-lint-i18n markdown`
`docs-lint-i18n content` |
| Charts | [`/doc`](https://gitlab.com/gitlab-org/charts/gitlab/-/tree/master/doc) | [`/doc-locale`](https://gitlab.com/gitlab-org/charts/gitlab/-/tree/master/doc-locale) | `check_docs_i18n_content`
`check_docs_i18n_markdown` |
| Operator | [`/doc`](https://gitlab.com/gitlab-org/cloud-native/gitlab-operator/-/tree/master/doc) | [`/doc-locale`](https://gitlab.com/gitlab-org/cloud-native/gitlab-operator/-/tree/master/doc-locale) | `docs-i18n-lint content`
`docs-i18n-lint markdown` |
-### Path verification of orphaned translation Files
+### Language-specific translation linting
+
+For languages with specific style requirements, we provide dedicated CI/CD jobs that run
+language-specific Vale rules. Each language has its own job that only runs when files
+in that language are modified.
+
+#### Japanese language support
+
+The `docs-i18n-lint japanese-vale` job runs Japanese-specific Vale linting:
+
+- Script: [`scripts/i18n_lint_language_vale.sh`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/scripts/i18n_lint_language_vale.sh)
+- Configuration: `doc-locale/ja-jp/.vale.ini`
+- Vale filter: `locale_rules`
+- Triggers: Only runs when these files change:
+ - `doc-locale/ja-jp/**/*.md` - Japanese documentation files
+ - `doc-locale/ja-jp/.vale.ini` - Japanese Vale configuration
+ - `scripts/i18n_lint_language_vale.sh` - Universal language linting script
+
+The job uses environment variables to configure the universal script:
+
+```yaml
+variables:
+ LANGUAGE_CODE: "ja-jp"
+ LANGUAGE_NAME: "Japanese"
+ VALE_FILTER: "locale_rules"
+ VALE_MIN_ALERT_LEVEL: "warning"
+```
+
+#### Adding new languages
+
+To add language-specific linting for additional languages:
+
+- Create language-specific Vale configuration at `doc-locale/LANGUAGE_CODE/.vale.ini`
+- Add a new CI job in `.gitlab/ci/docs.gitlab-ci.yml`:
+
+ ```yaml
+ docs-i18n-lint LANGUAGE-vale:
+ extends:
+ - .default-retry
+ - .docs:rules:docs-i18n-lint-LANGUAGE
+ - .docs-markdown-lint-image
+ stage: lint
+ needs: []
+ variables:
+ LANGUAGE_CODE: "LANGUAGE_CODE" # e.g., "fr-fr", "de-de"
+ LANGUAGE_NAME: "LANGUAGE_NAME" # e.g., "French", "German"
+ VALE_FILTER: "VALE_FILTER_NAME" # e.g., "french_rules"
+ VALE_MIN_ALERT_LEVEL: "warning" # or "error" for stricter rules
+ script:
+ - source ./scripts/utils.sh
+ - install_gitlab_gem
+ - scripts/i18n_lint_language_vale.sh
+ ```
+
+- Add triggering rules in `.gitlab/ci/rules.gitlab-ci.yml`:
+
+ ```yaml
+ .docs:rules:docs-i18n-lint-LANGUAGE:
+ rules:
+ - <<: *if-tech-docs-localization
+ changes:
+ - "doc-locale/LANGUAGE_CODE/**/*.md"
+ - "doc-locale/LANGUAGE_CODE/.vale.ini"
+ - "scripts/i18n_lint_language_vale.sh"
+ ```
+
+The universal script approach eliminates code duplication while maintaining language-specific
+customization through environment variables.
+
+### Path verification of orphaned translation files
The `docs-i18n-lint paths` job fails if translated files in `/doc-locale` have no corresponding English source files. The job runs when:
diff --git a/scripts/i18n_lint_language_vale.sh b/scripts/i18n_lint_language_vale.sh
new file mode 100755
index 0000000000000000000000000000000000000000..27d7658f230b14dc05225e068a86bd483448c747
--- /dev/null
+++ b/scripts/i18n_lint_language_vale.sh
@@ -0,0 +1,46 @@
+#!/usr/bin/env bash
+# Universal language-specific Vale linting script
+# Uses environment variables to configure language-specific behavior
+
+set -euo pipefail
+
+COLOR_RED="\e[31m"
+COLOR_GREEN="\e[32m"
+COLOR_RESET="\e[39m"
+
+# Optional environment variables with defaults
+VALE_FILTER="${VALE_FILTER:-locale_rules}"
+VALE_MIN_ALERT_LEVEL="${VALE_MIN_ALERT_LEVEL:-warning}"
+
+cd "$(dirname "$0")/.." || exit 1
+printf "INFO: Running ${LANGUAGE_NAME}-specific Vale linting at path $(pwd)...\n"
+printf "INFO: Language Code: ${LANGUAGE_CODE}\n"
+printf "INFO: Vale Filter: ${VALE_FILTER}\n"
+printf "INFO: Min Alert Level: ${VALE_MIN_ALERT_LEVEL}\n"
+
+ERRORCODE=0
+
+# shellcheck disable=SC2059
+printf "${COLOR_GREEN}INFO: Running ${LANGUAGE_NAME}-specific Vale rules...${COLOR_RESET}\n"
+
+# Run vale with either specified files or default path
+if [ $# -gt 0 ]; then
+ vale --config="doc-locale/${LANGUAGE_CODE}/.vale.ini" --minAlertLevel "${VALE_MIN_ALERT_LEVEL}" "$@" || {
+ printf "${COLOR_RED}ERROR: ${LANGUAGE_NAME} Vale rules found issues in translation files${COLOR_RESET}\n"
+ ((ERRORCODE++))
+ }
+else
+ vale --config="doc-locale/${LANGUAGE_CODE}/.vale.ini" --minAlertLevel "${VALE_MIN_ALERT_LEVEL}" "doc-locale/${LANGUAGE_CODE}/" || {
+ printf "${COLOR_RED}ERROR: ${LANGUAGE_NAME} Vale rules found issues in translation files${COLOR_RESET}\n"
+ ((ERRORCODE++))
+ }
+fi
+
+# Report results
+if [ "$ERRORCODE" -ne 0 ]; then
+ printf "\n${COLOR_RED}ERROR: ${LANGUAGE_NAME} Vale lint checks failed!${COLOR_RESET}\n"
+ exit 1
+else
+ printf "\n${COLOR_GREEN}SUCCESS: All ${LANGUAGE_NAME} Vale lint checks passed${COLOR_RESET}\n"
+ exit 0
+fi
diff --git a/spec/dot_gitlab_ci/rules_spec.rb b/spec/dot_gitlab_ci/rules_spec.rb
index 76a8a6a742431a70e45449c6db660f391dff7c16..14844d5cbc2d128017da0ba9f89008fa63117586 100644
--- a/spec/dot_gitlab_ci/rules_spec.rb
+++ b/spec/dot_gitlab_ci/rules_spec.rb
@@ -205,6 +205,7 @@
Dir.glob('*.md') +
Dir.glob('changelogs/*') +
Dir.glob('doc/.{markdownlint,vale}/**/*', File::FNM_DOTMATCH) +
+ Dir.glob('doc-locale/**/*', File::FNM_DOTMATCH) +
Dir.glob('node_modules/**/*', File::FNM_DOTMATCH) +
Dir.glob('patches/*') +
Dir.glob('public/assets/**/.*') +