Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 4 additions & 4 deletions .github/workflows/test-ja-translation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,19 @@ jobs:
- name: Configure translation credentials
shell: bash
env:
GCP_KEY: ${{ secrets.GCP_KEY }}
GCP_CREDENTIALS_B64: ${{ secrets.GCP_CREDENTIALS_B64 }}
GCP_PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }}
GCP_GLOSSARY_ID: ${{ secrets.GCP_GLOSSARY_ID }}
run: |
set -euo pipefail

if [ -z "${GCP_KEY}" ] || [ -z "${GCP_PROJECT_ID}" ] || [ -z "${GCP_GLOSSARY_ID}" ]; then
echo "Missing one or more required secrets: GCP_KEY, GCP_PROJECT_ID, GCP_GLOSSARY_ID" >&2
if [ -z "${GCP_CREDENTIALS_B64}" ] || [ -z "${GCP_PROJECT_ID}" ] || [ -z "${GCP_GLOSSARY_ID}" ]; then
echo "Missing one or more required secrets: GCP_CREDENTIALS_B64, GCP_PROJECT_ID, GCP_GLOSSARY_ID" >&2
exit 1
fi

CREDENTIALS_FILE="$RUNNER_TEMP/gcp-key.json"
echo "${GCP_KEY}" | base64 --decode > "${CREDENTIALS_FILE}"
printf '%s' "${GCP_CREDENTIALS_B64}" | base64 --decode > "${CREDENTIALS_FILE}"

echo "GOOGLE_APPLICATION_CREDENTIALS=${CREDENTIALS_FILE}" >> "$GITHUB_ENV"
echo "PROJECT_ID=${GCP_PROJECT_ID}" >> "$GITHUB_ENV"
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.DS_Store

Large diffs are not rendered by default.

118 changes: 118 additions & 0 deletions markdown-translator/src/customContentIndentation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
const CUSTOM_CONTENT_OPEN_RE = /^(\s*)<CustomContent\b[^>]*>\s*$/;
const CUSTOM_CONTENT_CLOSE_RE = /^(\s*)<\/CustomContent>\s*$/;
const CUSTOM_CONTENT_LINE_RE = /^(\s*)<\/?CustomContent\b[^>]*>\s*$/;
const FENCE_RE = /^(\s*)(`{3,}|~{3,})/;

const getLeadingSpaces = (line = "") => line.match(/^ */)?.[0].length ?? 0;

const isStandaloneCustomContentLine = (line = "") =>
CUSTOM_CONTENT_LINE_RE.test(line);

const getFenceMask = (lines) => {
const mask = Array(lines.length).fill(false);
let activeFence = null;

lines.forEach((line, index) => {
const fenceMatch = line.match(FENCE_RE);
if (!activeFence) {
if (fenceMatch) {
activeFence = {
marker: fenceMatch[2][0],
length: fenceMatch[2].length,
};
mask[index] = true;
}
return;
}

mask[index] = true;
if (
fenceMatch &&
fenceMatch[2][0] === activeFence.marker &&
fenceMatch[2].length >= activeFence.length
) {
activeFence = null;
}
});

return mask;
};

const findContextIndent = (lines, fenceMask, openIndex, currentIndent) => {
for (let index = openIndex - 1; index >= 0; index--) {
if (fenceMask[index]) {
continue;
}

const line = lines[index];
if (!line.trim() || isStandaloneCustomContentLine(line)) {
continue;
}

if (getLeadingSpaces(line) > currentIndent) {
continue;
}

return getLeadingSpaces(line);
}

return null;
};

const findInnerContentIndent = (lines, fenceMask, openIndex, closeIndex) => {
for (let index = openIndex + 1; index < closeIndex; index++) {
if (fenceMask[index]) {
continue;
}

const line = lines[index];
if (!line.trim() || isStandaloneCustomContentLine(line)) {
continue;
}

return getLeadingSpaces(line);
}

return null;
};

export const normalizeStandaloneCustomContentIndentation = (markdown = "") => {
const lines = markdown.split("\n");
const fenceMask = getFenceMask(lines);
const stack = [];

lines.forEach((line, index) => {
if (fenceMask[index]) {
return;
}

if (CUSTOM_CONTENT_OPEN_RE.test(line)) {
stack.push({
openIndex: index,
targetIndent: findContextIndent(
lines,
fenceMask,
index,
getLeadingSpaces(line)
),
});
return;
}

if (!CUSTOM_CONTENT_CLOSE_RE.test(line) || !stack.length) {
return;
}

const { openIndex, targetIndent } = stack.pop();
const normalizedIndent =
targetIndent ??
findInnerContentIndent(lines, fenceMask, openIndex, index) ??
getLeadingSpaces(lines[openIndex]);
lines[openIndex] = `${" ".repeat(normalizedIndent)}${lines[
openIndex
].trimStart()}`;
lines[index] = `${" ".repeat(normalizedIndent)}${lines[index].trimStart()}`;
});

return lines.join("\n");
};
5 changes: 4 additions & 1 deletion markdown-translator/src/gcpTranslator.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { visit } from "unist-util-visit";
// } from "remark-comment-o";

import { getMdFileList, writeFileSync, handleAstNode } from "./lib.js";
import { normalizeStandaloneCustomContentIndentation } from "./customContentIndentation.js";

const pSum = {
sum: 0,
Expand Down Expand Up @@ -74,7 +75,9 @@ export const gcpTranslator = async (filePath, outputFilePath) => {
gfmToMarkdown(),
],
});
const result = newFile.replaceAll(/(#+.+)(\\{)(#.+})/g, `$1{$3`);
const result = normalizeStandaloneCustomContentIndentation(
newFile.replaceAll(/(#+.+)(\\{)(#.+})/g, `$1{$3`)
);
writeFileSync(outputFilePath, result);
};

Expand Down
5 changes: 4 additions & 1 deletion markdown-translator/src/index_ja.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { getMdFileList, writeFileSync, handleAstNode } from "./lib.js";
import { loadVariables, variablesReplace } from "./variables.js";
import { postProcessFileFrontmatterAliases } from "./frontmatterAliases.js";
import { postProcessFileGithubMentions } from "./GithubMentions.js";
import { normalizeStandaloneCustomContentIndentation } from "./customContentIndentation.js";

const pSum = {
sum: 0,
Expand Down Expand Up @@ -77,7 +78,9 @@ const translateSingleMdToJa = async (filePath, outputFilePath) => {
gfmToMarkdown(),
],
});
const result = newFile.replaceAll(/(#+.+)(\\{)(#.+})/g, `$1{$3`);
const result = normalizeStandaloneCustomContentIndentation(
newFile.replaceAll(/(#+.+)(\\{)(#.+})/g, `$1{$3`)
);
writeFileSync(outputFilePath, result);
};

Expand Down
118 changes: 118 additions & 0 deletions markdown-translator/test/customContentIndentation.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import test from "node:test";
import assert from "node:assert/strict";

import { normalizeStandaloneCustomContentIndentation } from "../src/customContentIndentation.js";

test("normalizes standalone CustomContent indentation in ordered-list export sections", () => {
const input = [
" - export intro",
"",
" 3. before",
"",
" <CustomContent plan=\"starter,essential\">",
"",
" - export bullet",
"",
" 1. nested step",
"",
" </CustomContent>",
"",
" <CustomContent plan=\"premium\">",
"",
" - another export bullet",
"",
" </CustomContent>",
].join("\n");

const expected = [
" - export intro",
"",
" 3. before",
"",
" <CustomContent plan=\"starter,essential\">",
"",
" - export bullet",
"",
" 1. nested step",
"",
" </CustomContent>",
"",
" <CustomContent plan=\"premium\">",
"",
" - another export bullet",
"",
" </CustomContent>",
].join("\n");

assert.equal(normalizeStandaloneCustomContentIndentation(input), expected);
});

test("normalizes standalone CustomContent indentation in unordered-list descriptions", () => {
const input = [
"- description",
"",
" extra detail",
"",
" <CustomContent platform=\"tidb\">",
"",
" tidb-only note",
"",
" </CustomContent>",
"",
" <CustomContent platform=\"tidb-cloud\">",
"",
" cloud-only note",
"",
" </CustomContent>",
].join("\n");

const expected = [
"- description",
"",
" extra detail",
"",
" <CustomContent platform=\"tidb\">",
"",
" tidb-only note",
"",
" </CustomContent>",
"",
" <CustomContent platform=\"tidb-cloud\">",
"",
" cloud-only note",
"",
" </CustomContent>",
].join("\n");

assert.equal(normalizeStandaloneCustomContentIndentation(input), expected);
});

test("does not rewrite inline CustomContent or fenced code examples", () => {
const input = [
"Inline <CustomContent plan=\"ja\">value</CustomContent> stays inline.",
"",
"```md",
" <CustomContent plan=\"starter,essential\">",
" literal example",
" </CustomContent>",
"```",
].join("\n");

assert.equal(normalizeStandaloneCustomContentIndentation(input), input);
});

test("keeps already-correct nested CustomContent indentation unchanged", () => {
const input = [
" - parent bullet",
"",
" 1. nested step",
"",
" <CustomContent plan=\"starter,essential\">",
"",
" already aligned",
"",
" </CustomContent>",
].join("\n");

assert.equal(normalizeStandaloneCustomContentIndentation(input), input);
});