Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,31 @@ describe("findInSource", () => {
).toEqual([])
})

it("ignores a key cap, but not the sentence it sits in", () => {
// The pattern in F0AiChatTextArea: the cap reads what is printed on the
// key, the surrounding sentence is translated separately.
expect(
scanTsx(
"const A = () => <p><kbd>Enter</kbd> to select, or press <kbd>Esc</kbd></p>"
)
).toEqual(['<jsx-text> = "to select, or press"'])
})

it.each(["code", "pre", "samp", "var"])(
"ignores machine text inside <%s>",
(tag) => {
expect(
scanTsx(`const A = () => <${tag}>Delete the row</${tag}>`)
).toEqual([])
}
)

it("still flags prose in an element that merely contains a kbd", () => {
expect(
scanTsx("const A = () => <div>Press this key<kbd>Enter</kbd></div>")
).toEqual(['<jsx-text> = "Press this key"'])
})

it("does not look for JSX text in a .ts file", () => {
// A .ts file parsed as TSX turns `<T>` casts and comparisons into elements,
// and their contents into "JSX text".
Expand Down
31 changes: 30 additions & 1 deletion packages/react/.scripts/check-untranslated-copy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,17 @@ const STYLE_CONTAINERS = new Set([
"whileInView",
])

/**
* Elements whose text is machine or device text rather than product copy, so
* their children are skipped.
*
* `<kbd>` is the one that actually comes up: a key cap reads what is printed on
* the key, and the sentence around it is translated separately —
* `<kbd>Enter</kbd> {i18n.…navHint.select}`. The rest hold code, sample output
* and variable names, which are never translated either.
*/
const NON_COPY_ELEMENTS = new Set(["kbd", "code", "pre", "samp", "var"])

export type FindingKind =
| "jsx-text"
| "jsx-attribute"
Expand Down Expand Up @@ -404,6 +415,19 @@ export function findInSource(filePath: string, source: string): Finding[] {
}
}

/**
* Is this text a direct child of a `<kbd>`, `<code>`, … element? Only the
* immediate parent counts: a `<kbd>` inside a translated sentence must not
* silence the sentence, and prose nested under a `<pre>` would be a bug in
* the markup rather than something to hide.
*/
const inNonCopyElement = (node: ts.Node): boolean => {
const parent = node.parent
if (!parent || !ts.isJsxElement(parent)) return false
const tag = parent.openingElement.tagName.getText(sourceFile)
return NON_COPY_ELEMENTS.has(tag)
}

const visit = (node: ts.Node): void => {
if (ts.isJsxAttribute(node) && node.initializer) {
const attrName = node.name.getText(sourceFile)
Expand Down Expand Up @@ -443,7 +467,12 @@ export function findInSource(filePath: string, source: string): Finding[] {
}
}

if (isTsx && ts.isJsxText(node) && readsAsCopy(node.text)) {
if (
isTsx &&
ts.isJsxText(node) &&
!inNonCopyElement(node) &&
readsAsCopy(node.text)
) {
record(node, "<jsx-text>", node.text, "jsx-text")
}

Expand Down
8 changes: 1 addition & 7 deletions packages/react/.scripts/untranslated-copy-debt.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"note": "Untranslated user-visible copy in packages/react/src — string literals that should come from the i18n layer (src/lib/providers/i18n) instead. Enforced by .scripts/check-untranslated-copy.ts. This list may only shrink: translate a string and remove it from here (or run \"--update\"), never add one.",
"total": 133,
"total": 128,
"files": {
"src/components/F0Card/components/CardMetadata.tsx": [
"Unsupported property type:"
Expand All @@ -12,10 +12,6 @@
"Increase"
],
"src/components/F0Slider/F0SliderSkeleton.tsx": ["Loading slider"],
"src/components/F0VideoPlayer/components/AudioDescriptionToggleIcons.tsx": [
"AD",
"AD"
],
"src/components/OneChip/index.tsx": ["Close"],
"src/components/RichText/F0NotesTextEditor/F0NotesTextEditor.tsx": [
"Add paragraph"
Expand Down Expand Up @@ -103,15 +99,13 @@
"src/kits/ai/F0AiChat/components/markdownRenderers/components/Image.tsx": [
"Download"
],
"src/kits/ai/F0AiChatTextArea/F0AiChatTextArea.tsx": ["Enter", "Esc"],
"src/kits/Charts/CategoryBarChart/index.tsx": ["Category bar chart"],
"src/kits/surveys/SurveyAnsweringForm/SurveyAnsweringForm.tsx": ["Value"],
"src/kits/surveys/SurveyFormBuilder/lib.ts": ["New option 1"],
"src/kits/surveys/SurveyFormBuilder/QuestionTypes/BaseQuestion/ActionsMenu/useQuestionActions.ts": [
"Emojis"
],
"src/lib/F0GridStack/components/grid-stack-provider.tsx": ["No content"],
"src/lib/xray.tsx": ["XRay"],
"src/patterns/F0AnalyticsDashboard/components/DashboardGrid/DashboardGrid.tsx": [
"Drag to reorder"
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export const AudioDescriptionLineIcon = forwardRef<
vectorEffect="non-scaling-stroke"
/>
<text {...AD_TEXT_PROPS} fill="currentColor">
{/* i18n-exempt: glyph in the icon artwork, like the CC badge */}
AD
</text>
</svg>
Expand All @@ -67,6 +68,7 @@ export const AudioDescriptionFilledIcon = forwardRef<
<mask id={maskId} maskUnits="userSpaceOnUse">
<rect x={3} y={6} width={18} height={12} rx={3} fill="white" />
<text {...AD_TEXT_PROPS} fill="black">
{/* i18n-exempt: glyph in the icon artwork, like the CC badge */}
AD
</text>
</mask>
Expand Down
1 change: 1 addition & 0 deletions packages/react/src/lib/xray.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export const XRayProvider: React.FC<{ children: ReactNode }> = ({
typeof document !== "undefined" &&
createPortal(
<div className="bg-white fixed right-2 top-2 z-50 flex flex-col space-y-2 rounded-2xs border-solid border-f1-border p-4 opacity-80 shadow-md">
{/* i18n-exempt: name of the dev-only inspector overlay */}
<div className="text-md z-50 font-semibold">XRay</div>
<div className="flex flex-col space-y-2">
{componentTypes.map((type) => (
Expand Down
Loading