Skip to content

Commit 27a14d9

Browse files
docs: add a doc page for each locale (#3654)
Co-authored-by: DivisionByZero <[email protected]>
1 parent e8773e5 commit 27a14d9

File tree

6 files changed

+394
-84
lines changed

6 files changed

+394
-84
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ versions.json
8787
/docs/api/api-search-index.json
8888
/docs/public/api-diff-index.json
8989
/docs/public/faker.js
90+
/docs/locales/*.md
9091

9192
# Faker
9293
TAGS
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
<script setup lang="ts">
2+
import { onMounted } from 'vue';
3+
import { formatResult } from './format';
4+
5+
type FakerModule = Awaited<typeof import('@faker-js/faker')>;
6+
7+
const { target } = defineProps<{ target: string }>();
8+
9+
let pendingFrame: number | null = null;
10+
let fakerModulePromise: Promise<FakerModule> | null = null;
11+
const invocationEndPattern = /^([^ ].*)?\)(\.\w+)?;? ?(\/\/|$)/;
12+
13+
function scheduleEnhancement(): void {
14+
if (pendingFrame != null) {
15+
cancelAnimationFrame(pendingFrame);
16+
}
17+
18+
pendingFrame = requestAnimationFrame(() => {
19+
pendingFrame = null;
20+
void enhanceCodeBlocks();
21+
});
22+
}
23+
24+
async function enhanceCodeBlocks(): Promise<void> {
25+
const blocks = Array.from(
26+
document.querySelectorAll<HTMLElement>('pre > code')
27+
).filter((block) => block.textContent?.includes('@faker-js/faker'));
28+
29+
if (blocks.length === 0) {
30+
return;
31+
}
32+
33+
const fakerModule = await loadFakerModule();
34+
if (fakerModule == null) {
35+
return;
36+
}
37+
38+
for (const block of blocks) {
39+
processCodeBlock(block, fakerModule);
40+
}
41+
}
42+
43+
async function loadFakerModule(): Promise<FakerModule | null> {
44+
if (fakerModulePromise == null) {
45+
try {
46+
fakerModulePromise = import('@faker-js/faker') as Promise<FakerModule>;
47+
} catch (error) {
48+
console.error('[ApiDocsLocale] Failed to load Faker module.', error);
49+
return null;
50+
}
51+
}
52+
53+
try {
54+
return await fakerModulePromise;
55+
} catch (error) {
56+
console.error('[ApiDocsLocale] Failed to load Faker module.', error);
57+
return null;
58+
}
59+
}
60+
61+
function processCodeBlock(block: HTMLElement, fakerModule: FakerModule): void {
62+
block.querySelectorAll('.comment-delete-marker').forEach((el) => el.remove());
63+
64+
const domLines = block.querySelectorAll<HTMLElement>('.line');
65+
if (domLines.length === 0) {
66+
return;
67+
}
68+
69+
const fakerInstance = resolveFakerInstance(fakerModule);
70+
if (fakerInstance == null) {
71+
return;
72+
}
73+
74+
const invocations = collectInvocations(domLines, target, fakerInstance);
75+
76+
for (const { domLine, expression, varName, fakerInstance } of invocations) {
77+
const evaluation = evaluateExpression(expression, varName, fakerInstance);
78+
const rendered =
79+
evaluation instanceof Error
80+
? `Error: ${evaluation.message}`
81+
: formatResult(evaluation);
82+
83+
insertComment(domLine, rendered);
84+
}
85+
}
86+
87+
function resolveFakerInstance(fakerModule: FakerModule): unknown | null {
88+
return (fakerModule as Record<string, unknown>)[target] ?? null;
89+
}
90+
91+
function collectInvocations(
92+
domLines: NodeListOf<HTMLElement>,
93+
varName: string,
94+
fakerInstance: unknown
95+
) {
96+
const targets: Array<{
97+
domLine: HTMLElement;
98+
expression: string;
99+
varName: string;
100+
fakerInstance: unknown;
101+
}> = [];
102+
103+
if (!varName) {
104+
return targets;
105+
}
106+
107+
for (let index = 0; index < domLines.length; index++) {
108+
const domLine = domLines[index];
109+
const text = domLine.textContent ?? '';
110+
111+
if (!text.includes(`${varName}.`)) {
112+
continue;
113+
}
114+
115+
const startIndex = index;
116+
117+
while (
118+
index < domLines.length &&
119+
!invocationEndPattern.test(domLines[index]?.textContent ?? '')
120+
) {
121+
index++;
122+
}
123+
124+
if (index >= domLines.length) {
125+
break;
126+
}
127+
128+
const endIndex = index;
129+
const expression = extractExpression(domLines, startIndex, endIndex);
130+
const expressionVarName = expression.match(/^([\w$]+)/)?.[1] ?? '';
131+
132+
if (!expression || expressionVarName !== varName) {
133+
continue;
134+
}
135+
136+
targets.push({
137+
domLine: domLines[endIndex]!,
138+
expression,
139+
varName,
140+
fakerInstance,
141+
});
142+
}
143+
144+
return targets;
145+
}
146+
147+
function extractExpression(
148+
domLines: NodeListOf<HTMLElement>,
149+
startIndex: number,
150+
endIndex: number
151+
): string {
152+
const chunks: string[] = [];
153+
154+
for (let i = startIndex; i <= endIndex; i++) {
155+
const text = domLines[i]?.textContent ?? '';
156+
const commentIndex = text.indexOf('//');
157+
const code = commentIndex === -1 ? text : text.slice(0, commentIndex);
158+
chunks.push(code);
159+
}
160+
161+
return chunks.join('\n').trim().replace(/;\s*$/, '');
162+
}
163+
164+
function evaluateExpression(
165+
expression: string,
166+
varName: string,
167+
fakerInstance: unknown
168+
): unknown {
169+
try {
170+
const fn = new Function(
171+
varName,
172+
`"use strict"; return (${expression});`
173+
) as (fakerArg: unknown) => unknown;
174+
175+
return fn(fakerInstance);
176+
} catch (error) {
177+
return error instanceof Error
178+
? error
179+
: new Error(String(error ?? 'Unknown error'));
180+
}
181+
}
182+
183+
function insertComment(domLine: HTMLElement, content: string): void {
184+
const lines = content.split('\n');
185+
186+
if (lines.length === 1) {
187+
ensureTrailingSpace(domLine);
188+
domLine.insertAdjacentHTML('beforeend', newCommentSpan(lines[0]!));
189+
return;
190+
}
191+
192+
for (const line of [...lines].reverse()) {
193+
domLine.insertAdjacentHTML('afterend', newCommentLine(line));
194+
}
195+
}
196+
197+
function ensureTrailingSpace(domLine: HTMLElement): void {
198+
const lastElement = domLine.lastElementChild as HTMLElement | null;
199+
if (lastElement != null) {
200+
const text = lastElement.textContent ?? '';
201+
if (!/\s$/.test(text)) {
202+
lastElement.textContent = `${text} `;
203+
}
204+
return;
205+
}
206+
207+
if (!/\s$/.test(domLine.textContent ?? '')) {
208+
domLine.append(' ');
209+
}
210+
}
211+
212+
function newCommentLine(content: string): string {
213+
return `<span class="line comment-delete-marker">
214+
${newCommentSpan(content)}
215+
</span>`;
216+
}
217+
218+
function newCommentSpan(content: string): string {
219+
return `<span class="comment-delete-marker" style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// ${content}</span>`;
220+
}
221+
222+
onMounted(() => {
223+
scheduleEnhancement();
224+
});
225+
</script>
226+
227+
<template>
228+
<span class="api-docs-locale-hook" style="display: none" />
229+
</template>

docs/.vitepress/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ For a full list of all methods please refer to https://fakerjs.dev/api/\`, logSt
254254

255255
sidebar: {
256256
'/guide/': getSideBarWithExpandedEntry('Guide'),
257+
'/locales/': getSideBarWithExpandedEntry('Guide'),
257258
'/api/': getSideBarWithExpandedEntry('API'),
258259
'/contributing/': getSideBarWithExpandedEntry('Contributing'),
259260
'/about/': getSideBarWithExpandedEntry('About'),

0 commit comments

Comments
 (0)