Skip to content

Commit bb411de

Browse files
committed
docs: translation improvement
1 parent 5605a6c commit bb411de

143 files changed

Lines changed: 16891 additions & 521 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"lang:clean": "bun packages/docs/src/lib/scripts/removeUnusedTranslations.js",
1818
"lang:sort": "bun packages/docs/src/lib/scripts/sortTranslations.js",
1919
"lang:fill": "bun packages/docs/src/lib/scripts/fillTranslations.js",
20+
"lang:add": "bun packages/docs/src/lib/scripts/addTranslations.js",
2021
"lint": "bunx -y oxlint@latest --ignore-pattern packages/playground",
2122
"test": "bun test",
2223
"wallace": "bunx -y wallace-cli packages/daisyui/daisyui.css",

packages/docs/src/lib/mdsvex/mdsvex.config.js

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@ import remarkGithub from "remark-github"
88
import remarkCodeTitles from "remark-flexible-code-titles"
99
// import toc from "@jsdevtools/rehype-toc"
1010
import rehypeSlug from "rehype-slug"
11-
import linkHeadings from "rehype-autolink-headings"
11+
import { remarkLinkHeadings } from "./remark-link-headings.js"
1212
import rehypeExternalLinks from "rehype-external-links"
1313
import { visit } from "unist-util-visit"
1414
import { remarkRenderComponent } from "./remark-render-component.js"
15+
import { remarkTranslate } from "./remark-translate.js"
1516

1617
const __filename = fileURLToPath(import.meta.url)
1718
const __dirname = path.dirname(__filename)
@@ -96,28 +97,28 @@ const rehypePlugins = [
9697
// },
9798
// },
9899
// ],
99-
[
100-
linkHeadings,
101-
{
102-
behavior: "prepend",
103-
content: {
104-
type: "element",
105-
tagName: "span",
106-
properties: {
107-
className: [
108-
"heading-anchorlink-icon bg-base-content/5 hover:bg-primary/10 size-[1em] text-base-content/30 hover:text-primary/50 rounded-field border border-base-content/5 hover:border-primary/20 inline-grid place-content-center hover:shadow-sm hover:shadow-base-200 align-text-bottom me-3 lg:absolute lg:ms-[-1.5em] lg:mt-1 transition-all group",
109-
],
110-
},
111-
children: [
112-
{
113-
type: "text",
114-
value:
115-
'<svg class="group-hover:scale-100 scale-90 transition-transform" fill="currentColor" width=".5em" height=".5em" viewBox="0 0 256 256" id="Flat" xmlns="http://www.w3.org/2000/svg" ><path d="M216,148H172V108h44a12,12,0,0,0,0-24H172V40a12,12,0,0,0-24,0V84H108V40a12,12,0,0,0-24,0V84H40a12,12,0,0,0,0,24H84v40H40a12,12,0,0,0,0,24H84v44a12,12,0,0,0,24,0V172h40v44a12,12,0,0,0,24,0V172h44a12,12,0,0,0,0-24Zm-108,0V108h40v40Z"/></svg>',
116-
},
117-
],
118-
},
119-
},
120-
],
100+
// [
101+
// linkHeadings,
102+
// {
103+
// behavior: "prepend",
104+
// content: {
105+
// type: "element",
106+
// tagName: "span",
107+
// properties: {
108+
// className: [
109+
// "heading-anchorlink-icon bg-base-content/5 hover:bg-primary/10 size-[1em] text-base-content/30 hover:text-primary/50 rounded-field border border-base-content/5 hover:border-primary/20 inline-grid place-content-center hover:shadow-sm hover:shadow-base-200 align-text-bottom me-3 lg:absolute lg:ms-[-1.5em] lg:mt-1 transition-all group",
110+
// ],
111+
// },
112+
// children: [
113+
// {
114+
// type: "text",
115+
// value:
116+
// '<svg class="group-hover:scale-100 scale-90 transition-transform" fill="currentColor" width=".5em" height=".5em" viewBox="0 0 256 256" id="Flat" xmlns="http://www.w3.org/2000/svg" ><path d="M216,148H172V108h44a12,12,0,0,0,0-24H172V40a12,12,0,0,0-24,0V84H108V40a12,12,0,0,0-24,0V84H40a12,12,0,0,0,0,24H84v40H40a12,12,0,0,0,0,24H84v44a12,12,0,0,0,24,0V172h40v44a12,12,0,0,0,24,0V172h44a12,12,0,0,0,0-24Zm-108,0V108h40v40Z"/></svg>',
117+
// },
118+
// ],
119+
// },
120+
// },
121+
// ],
121122
[rehypeExternalLinks, { rel: ["nofollow"], target: ["_blank"] }],
122123
]
123124

@@ -138,14 +139,16 @@ const remarkPlugins = [
138139
],
139140
replacePlaceholders,
140141
remarkRenderComponent,
142+
remarkLinkHeadings,
143+
remarkTranslate,
141144
]
142145

143146
export const mdsvexExtensions = [".svx", ".md"]
144147

145148
const config = {
146149
extensions: mdsvexExtensions,
147-
rehypePlugins: rehypePlugins,
148150
remarkPlugins: remarkPlugins,
151+
rehypePlugins: rehypePlugins, // Keep rehypePlugins after remarkPlugins
149152
layout: {
150153
components: "src/lib/mdsvex/layout-components.svelte",
151154
blog: "src/lib/mdsvex/layout-blog.svelte",
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { visit } from "unist-util-visit"
2+
3+
export function remarkLinkHeadings() {
4+
return (tree) => {
5+
visit(tree, "heading", (node) => {
6+
if (node.depth >= 2) {
7+
// Collect text from text nodes for the ID
8+
let headingText = ""
9+
node.children.forEach((child) => {
10+
if (child.type === "text") {
11+
headingText += child.value
12+
} else if (child.type === "html" && child.value.includes('<Translate text="')) {
13+
// Extract text from Translate component
14+
const match = child.value.match(/<Translate text="([^"]+)"/)
15+
if (match && match[1]) {
16+
headingText += match[1].replace(/&quot;/g, '"')
17+
}
18+
}
19+
})
20+
21+
// Create id from heading text
22+
const id = headingText
23+
.toLowerCase()
24+
.replace(/[^\w\s-]/g, "")
25+
.replace(/\s+/g, "-")
26+
27+
// Create the link icon element wrapped in an anchor tag
28+
const linkIconWithAnchor = {
29+
type: "html",
30+
value: `<a href="#${id}" class="no-underline">
31+
<span class="heading-anchorlink-icon bg-base-content/5 hover:bg-primary/10 size-[1em] text-base-content/30 hover:text-primary/50 rounded-field border border-base-content/5 hover:border-primary/20 inline-grid place-content-center hover:shadow-sm hover:shadow-base-200 align-text-bottom me-3 lg:absolute lg:ms-[-1.5em] lg:mt-1 transition-all group">
32+
<svg class="group-hover:scale-100 scale-90 transition-transform" fill="currentColor" width=".5em" height=".5em" viewBox="0 0 256 256" id="Flat" xmlns="http://www.w3.org/2000/svg">
33+
<path d="M216,148H172V108h44a12,12,0,0,0,0-24H172V40a12,12,0,0,0-24,0V84H108V40a12,12,0,0,0-24,0V84H40a12,12,0,0,0,0,24H84v40H40a12,12,0,0,0,0,24H84v44a12,12,0,0,0,24,0V172h40v44a12,12,0,0,0,24,0V172h44a12,12,0,0,0,0-24Zm-108,0V108h40v40Z"/>
34+
</svg>
35+
</span>
36+
</a>`,
37+
}
38+
39+
// Add the link icon at the beginning of the heading
40+
node.children.unshift(linkIconWithAnchor)
41+
42+
// Add id to the heading for linking
43+
if (!node.data) node.data = {}
44+
if (!node.data.hProperties) node.data.hProperties = {}
45+
node.data.hProperties.id = id
46+
}
47+
})
48+
}
49+
}
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import { visit } from "unist-util-visit"
2+
import path from "path"
3+
4+
// Helper function to escape quotes in the text
5+
const escapeQuotes = (text) => text.replace(/"/g, "&quot;")
6+
7+
// Creates a Translate node for a given text
8+
const createTranslateNode = (text) => ({
9+
type: "html",
10+
value: `<Translate text="${escapeQuotes(text)}" />`,
11+
})
12+
13+
export function remarkTranslate() {
14+
return (tree, file) => {
15+
// Skip processing if file is CHANGELOG.md
16+
const filename = file.filename || file.history?.[0] || ""
17+
const basename = path.basename(filename)
18+
19+
if (basename === "CHANGELOG.md") {
20+
return
21+
}
22+
23+
// For headings, preserve original text in a property and add a wrapper with id
24+
visit(tree, "heading", (node) => {
25+
if (node.children && node.children.length) {
26+
const textNodes = node.children.filter((child) => child.type === "text")
27+
28+
if (textNodes.length) {
29+
const headingText = textNodes.map((textNode) => textNode.value).join("")
30+
31+
// Generate an ID for the heading based on the text
32+
const headingId = headingText
33+
.toLowerCase()
34+
.replace(/[^\w\s-]/g, "")
35+
.replace(/\s+/g, "-")
36+
37+
// Create a wrapper span that contains the Translate component
38+
// but preserves the original text for ID generation
39+
const wrapperNode = {
40+
type: "html",
41+
value: `<span data-heading-text="${escapeQuotes(headingText)}">${createTranslateNode(headingText).value}</span>`,
42+
}
43+
44+
// Replace text nodes with our wrapper
45+
node.children = node.children.map((child) =>
46+
child.type === "text" ? wrapperNode : child,
47+
)
48+
49+
// Ensure the heading has the correct ID
50+
if (!node.data) node.data = {}
51+
if (!node.data.hProperties) node.data.hProperties = {}
52+
node.data.hProperties.id = headingId
53+
}
54+
}
55+
})
56+
57+
// Process table cells individually
58+
visit(tree, ["table"], (tableNode) => {
59+
if (tableNode.children) {
60+
// For each row
61+
tableNode.children.forEach((row) => {
62+
if (row.children) {
63+
// For each cell in the row
64+
row.children.forEach((cell) => {
65+
if (cell.children) {
66+
// For each element in the cell
67+
cell.children.forEach((child, index) => {
68+
if (child.type === "text" && child.value.trim()) {
69+
// Replace text with Translate component
70+
cell.children[index] = createTranslateNode(child.value.trim())
71+
} else if (child.type === "paragraph" && child.children) {
72+
// For paragraphs inside cells
73+
child.children.forEach((grandChild, grandIndex) => {
74+
if (grandChild.type === "text" && grandChild.value.trim()) {
75+
child.children[grandIndex] = createTranslateNode(grandChild.value.trim())
76+
}
77+
})
78+
} else if (child.type === "inlineCode") {
79+
// Keep code formatting
80+
const code = child.value
81+
cell.children[index] = {
82+
type: "html",
83+
value: `<code>${code}</code>`,
84+
}
85+
}
86+
})
87+
}
88+
})
89+
}
90+
})
91+
}
92+
})
93+
94+
// Process paragraphs
95+
visit(tree, "paragraph", (node) => {
96+
if (node.children && node.children.length) {
97+
node.children.forEach((child, index) => {
98+
if (child.type === "text") {
99+
// Split text by line breaks to preserve formatting
100+
const lines = child.value.split(/(\n+)/)
101+
if (lines.length > 1) {
102+
// If there are line breaks, create multiple translate nodes
103+
const newNodes = []
104+
lines.forEach((line, i) => {
105+
if (i % 2 === 0 && line.trim()) {
106+
// Text content
107+
newNodes.push(createTranslateNode(line))
108+
} else {
109+
// Line breaks
110+
newNodes.push({ type: "text", value: line })
111+
}
112+
})
113+
node.children.splice(index, 1, ...newNodes)
114+
} else if (child.value.trim()) {
115+
// Simple text without line breaks
116+
node.children[index] = createTranslateNode(child.value)
117+
}
118+
}
119+
})
120+
}
121+
})
122+
123+
// Process list items
124+
visit(tree, "listItem", (node) => {
125+
if (node.children && node.children.length) {
126+
node.children.forEach((child) => {
127+
if (child.type === "paragraph" && child.children) {
128+
child.children.forEach((grandChild, index) => {
129+
if (grandChild.type === "text" && grandChild.value.trim()) {
130+
child.children[index] = createTranslateNode(grandChild.value)
131+
}
132+
})
133+
}
134+
})
135+
}
136+
})
137+
138+
// Process emphasis and strong
139+
visit(tree, ["emphasis", "strong"], (node) => {
140+
if (node.children && node.children.length) {
141+
node.children.forEach((child, index) => {
142+
if (child.type === "text" && child.value.trim()) {
143+
node.children[index] = createTranslateNode(child.value)
144+
}
145+
})
146+
}
147+
})
148+
149+
// Process blockquotes
150+
visit(tree, "blockquote", (node) => {
151+
if (node.children && node.children.length) {
152+
node.children.forEach((child) => {
153+
if (child.type === "paragraph" && child.children) {
154+
child.children.forEach((grandChild, index) => {
155+
if (grandChild.type === "text" && grandChild.value.trim()) {
156+
child.children[index] = createTranslateNode(grandChild.value)
157+
}
158+
})
159+
}
160+
})
161+
}
162+
})
163+
}
164+
}

0 commit comments

Comments
 (0)