Skip to content

Conversation

@Amato21
Copy link

@Amato21 Amato21 commented Dec 29, 2025

Hi @chhoumann,

I have implemented a complete i18n system to make QuickAdd multilingual, and I've added the French translation.

Changes implemented:

  • Core: Added src/i18n/i18n.ts and updated tsconfig.json to support JSON imports (resolveJsonModule).
  • Locales: Added en.json (default) and fr.json.
  • Settings: Fully translated quickAddSettingsTab.ts.
  • Builders: Fully translated choiceBuilder.ts, templateChoiceBuilder.ts, captureChoiceBuilder.ts, and MacroBuilder.ts.
  • Main: Translated command palette names and ribbon icon tooltip in main.ts.

Status:

  • Settings Tab
  • Choice Builder (Generic)
  • Template Choice Builder
  • Capture Choice Builder
  • Macro Builder (Rename & Run on startup)

This structure allows users to easily add more languages in the future by simply adding a JSON file in src/i18n/locales/.

Let me know if you need any changes!

Summary by CodeRabbit

  • New Features

    • Multi-language support now available: Complete English and French localization across all interface elements, including settings, commands, builder options, and macro configurations. Users can interact with the application in their preferred language.
  • Chores

    • Updated TypeScript configuration to support JSON module imports and module interoperability.

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link

vercel bot commented Dec 29, 2025

@Amato21 is attempting to deploy a commit to the Christian Bager Bach Houmann's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link

coderabbitai bot commented Dec 29, 2025

📝 Walkthrough

Walkthrough

This pull request introduces an internationalization (i18n) system to the codebase. It adds a new i18n utility module that resolves translation keys from locale JSON files, updates multiple UI builder and settings files to use the new translation function instead of hardcoded strings, adds English and French locale resources, and configures TypeScript to support JSON module imports.

Changes

Cohort / File(s) Summary
ChoiceBuilder UI Localization
src/gui/ChoiceBuilder/captureChoiceBuilder.ts, src/gui/ChoiceBuilder/choiceBuilder.ts, src/gui/ChoiceBuilder/templateChoiceBuilder.ts
Replaced hardcoded Setting names, descriptions, placeholder texts, and dropdown option labels with i18n key lookups via t() function. Includes headers (Location, Position, Linking, Content, Behavior), preview labels, and nested option strings. Control flow unchanged.
Macro & Main UI Localization
src/gui/MacroGUIs/MacroBuilder.ts, src/main.ts
Replaced hardcoded UI strings for macro commands (renaming, run on startup), branch titles, and command names (runQuickAdd, reloadQuickAdd, testQuickAdd) with i18n lookups. Ribbon label also localized.
Settings Tab Localization
src/quickAddSettingsTab.ts
Replaced hard-coded section headers, group names, button texts, descriptions, and inline labels across all settings sections with translated equivalents via t() function. State management and control flow preserved.
i18n Infrastructure
src/i18n/i18n.ts
New utility module exporting t(path: string): string function. Imports locale maps, resolves nested keys via dot-path traversal, implements fallback to English if current language unavailable, and returns path as fallback if key is missing.
Locale Resources
src/i18n/locales/en.json, src/i18n/locales/fr.json
New locale JSON files providing complete English and French translations for commands, settings, builder UI components, macro controls, and related descriptors. Structured hierarchically by domain (commands, settings, builder, macro).
Build Configuration
tsconfig.json
Added resolveJsonModule and esModuleInterop compiler options to support JSON file imports and CommonJS/ES module interoperability.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 Whiskers twitch with joy so bright,
Keys and translations set just right,
English, French, now side by side,
Global voices, far and wide!
Locale files bloom, the code takes flight, 🌍✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: implementing i18n support and adding French translation across Settings, Builders, and Macros components.
✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (21)
src/gui/ChoiceBuilder/captureChoiceBuilder.ts (13)

96-122: Incomplete localization: dropdown options remain hardcoded.

Lines 103-104 contain hardcoded English strings "Use selection" and "Ignore selection" that should be localized for consistency with the translated setting name and description.

🔎 Proposed localization fix
 			.addDropdown((dropdown) => {
 				dropdown.addOptions({
 					"": t("builder.one_page_override.follow"),
-					enabled: "Use selection",
-					disabled: "Ignore selection",
+					enabled: t("builder.capture.selection_options.use"),
+					disabled: t("builder.capture.selection_options.ignore"),
 				});

156-159: Incomplete localization: setting name and description remain hardcoded.

Lines 157-158 contain hardcoded English strings that should be localized to match the pattern used elsewhere in this file.

🔎 Proposed localization fix
 			new Setting(captureToFileContainer)
-				.setName("File path / format")
-				.setDesc("Choose a file or use format syntax (e.g., {{DATE}})");
+				.setName(t("builder.capture.file_path"))
+				.setDesc(t("builder.capture.file_path_desc"));

174-179: Incomplete localization: placeholder remains hardcoded.

Line 178 contains a hardcoded placeholder "File name format" that should be localized.

🔎 Proposed localization fix
 		createValidatedInput({
 			app: this.app,
 			parent: captureToFileContainer,
 			initialValue: this.choice.captureTo,
-			placeholder: "File name format",
+			placeholder: t("builder.capture.file_name_format"),
 			suggestions: markdownFilesAndFormatSyntax,

450-468: Incomplete localization: placeholder and error message remain hardcoded.

Lines 454 and 456 contain hardcoded English strings that should be localized.

🔎 Proposed localization fix
 		createValidatedInput({
 			app: this.app,
 			parent: this.contentEl,
 			initialValue: this.choice.insertAfter.after,
-			placeholder: "Insert after",
+			placeholder: t("builder.capture.insert_after"),
 			required: true,
-			requiredMessage: "Insert after text is required",
+			requiredMessage: t("builder.capture.insert_after_required"),
 			attachSuggesters: [

500-511: Incomplete localization: setting name and description remain hardcoded.

Lines 502-503 contain hardcoded English strings that should be localized.

🔎 Proposed localization fix
 		if (this.choice.insertAfter.inline) {
 			new Setting(this.contentEl)
-				.setName("Replace existing value")
-				.setDesc("Replace everything after the matched text up to end-of-line.")
+				.setName(t("builder.capture.replace_existing"))
+				.setDesc(t("builder.capture.replace_existing_desc"))
 				.addToggle((toggle) =>

516-529: Incomplete localization: description remains hardcoded.

Lines 519-520 contain a hardcoded description that should be localized.

🔎 Proposed localization fix
 			insertAtEndSetting
 				.setName(t("builder.capture.insert_end_section"))
 				.setDesc(
-					"Place the text at the end of the matched section instead of the top.",
+					t("builder.capture.insert_end_section_desc"),
 				)

535-562: Incomplete localization: setting name, descriptions, and dropdown options remain hardcoded.

Lines 540-550 contain multiple hardcoded English strings that should be localized.

🔎 Proposed localization fix
 			blankLineModeSetting
-				.setName("Blank lines after match")
+				.setName(t("builder.capture.blank_lines_after_match"))
 				.setDesc(
 					insertAtEndEnabled
-						? "Not used when inserting at end of section."
-						: blankLineModeDesc,
+						? t("builder.capture.blank_lines_not_used")
+						: t("builder.capture.blank_lines_desc"),
 				)
 					.addDropdown((dropdown) => {
 						dropdown
-							.addOption("auto", "Auto (headings only)")
-							.addOption("skip", "Always skip")
-							.addOption("none", "Never skip")
+							.addOption("auto", t("builder.capture.blank_lines_options.auto"))
+							.addOption("skip", t("builder.capture.blank_lines_options.skip"))
+							.addOption("none", t("builder.capture.blank_lines_options.none"))

564-592: Incomplete localization: description and Notice message remain hardcoded.

Lines 567 and 587 contain hardcoded English text that should be localized.

🔎 Proposed localization fix
 			new Setting(this.contentEl)
 				.setName(t("builder.capture.consider_subsections"))
 				.setDesc(
-					"Also include the section's subsections (requires target to be a heading starting with #).",
+					t("builder.capture.consider_subsections_desc"),
 				)
 				.addToggle((toggle) =>
 					toggle
 						.setValue(this.choice.insertAfter?.considerSubsections)
 						.onChange((value) => {
 							if (!value) {
 								this.choice.insertAfter.considerSubsections = false;
 								return;
 							}
 
 							const targetIsHeading =
 								this.choice.insertAfter.after.startsWith("#");
 							if (targetIsHeading) {
 								this.choice.insertAfter.considerSubsections = value;
 							} else {
 								this.choice.insertAfter.considerSubsections = false;
 								// reset the toggle to match state and inform user
 								toggle.setValue(false);
 								new Notice(
-									"Consider subsections requires the target to be a heading (starts with #)",
+									t("builder.capture.consider_subsections_notice"),
 								);
 							}
 						}),

594-623: Incomplete localization: setting name, description, and dropdown options remain hardcoded.

Lines 596-597 and 614-616 contain hardcoded English strings that should be localized.

🔎 Proposed localization fix
 		createLineIfNotFound
-			.setName("Create line if not found")
-			.setDesc("Creates the 'insert after' line if it is not found.")
+			.setName(t("builder.capture.create_line_if_not_found"))
+			.setDesc(t("builder.capture.create_line_if_not_found_desc"))
 			.addToggle((toggle) => {
 				if (!this.choice.insertAfter?.createIfNotFound)
 					this.choice.insertAfter.createIfNotFound = false; // Set to default
 
 				toggle
 					.setValue(this.choice.insertAfter?.createIfNotFound)
 					.onChange(
 						(value) => (this.choice.insertAfter.createIfNotFound = value),
 					).toggleEl.style.marginRight = "1em";
 			})
 			.addDropdown((dropdown) => {
 				if (!this.choice.insertAfter?.createIfNotFoundLocation)
 					this.choice.insertAfter.createIfNotFoundLocation =
 						CREATE_IF_NOT_FOUND_TOP; // Set to default
 
 				dropdown
-					.addOption(CREATE_IF_NOT_FOUND_TOP, "Top")
-					.addOption(CREATE_IF_NOT_FOUND_BOTTOM, "Bottom")
-					.addOption(CREATE_IF_NOT_FOUND_CURSOR, "Cursor")
+					.addOption(CREATE_IF_NOT_FOUND_TOP, t("builder.capture.position_options.top"))
+					.addOption(CREATE_IF_NOT_FOUND_BOTTOM, t("builder.capture.position_options.bottom"))
+					.addOption(CREATE_IF_NOT_FOUND_CURSOR, t("builder.capture.position_options.cursor"))

651-670: Incomplete localization: placeholder and error message remain hardcoded.

Lines 656 and 658 contain hardcoded English strings that should be localized.

🔎 Proposed localization fix
 		const formatHandle = createValidatedInput({
 			app: this.app,
 			parent: this.contentEl,
 			inputKind: "textarea",
 			initialValue: this.choice.format.format,
-			placeholder: "Format",
+			placeholder: t("builder.capture.format"),
 			required: this.choice.format.enabled,
-			requiredMessage: "Capture format is required when enabled",
+			requiredMessage: t("builder.capture.format_required"),
 			attachSuggesters: [

725-740: Incomplete localization: placeholder and error message remain hardcoded.

Lines 729 and 735 contain hardcoded English strings that should be localized.

🔎 Proposed localization fix
 		const templateSelectorHandle = createValidatedInput({
 			app: this.app,
 			parent: this.contentEl,
 			initialValue: this.choice?.createFileIfItDoesntExist?.template ?? "",
-			placeholder: "Template path",
+			placeholder: t("builder.common.template_path"),
 			suggestions: templateFilePaths,
 			maxSuggestions: 50,
 			validator: (raw) => {
 				const v = raw.trim();
 				if (!v) return true;
-				return templateFilePaths.includes(v) || "Template not found";
+				return templateFilePaths.includes(v) || t("builder.common.template_not_found");
 			},
 			onChange: (value) => {

67-73: Incomplete localization: hardcoded description string.

Line 68 contains a hardcoded English string "Open the captured file." that should be localized.

🔎 Proposed localization fix
 		if (!this.choice.captureToActiveFile) {
-			this.addOpenFileSetting("Open the captured file.");
+			this.addOpenFileSetting(t("builder.capture.open_file_desc"));

79-94: Incomplete localization: Templater setting not localized.

Lines 81-83 contain hardcoded English strings for the Templater setting name and description that should be localized for consistency.

🔎 Proposed localization fix
 	private addTemplaterAfterCaptureSetting() {
 		new Setting(this.contentEl)
-			.setName("Run Templater on entire destination file after capture")
+			.setName(t("builder.capture.templater_after_capture"))
 			.setDesc(
-				"Advanced / legacy: this executes any `<% %>` anywhere in the destination file (including inside code blocks).",
+				t("builder.capture.templater_after_capture_desc"),
 			)
 			.addToggle((toggle) => {
src/gui/ChoiceBuilder/templateChoiceBuilder.ts (8)

101-114: Incomplete localization: description remains hardcoded.

Line 106 contains a hardcoded description "Set the file name format." that should be localized.

🔎 Proposed localization fix
 		enableSetting
 			.setName(t("builder.template.file_name_format"))
-			.setDesc("Set the file name format.")
+			.setDesc(t("builder.template.file_name_format_desc"))
 			.addToggle((toggleComponent) => {

83-98: Incomplete localization: error message remains hardcoded.

Line 93 contains a hardcoded error message "Template not found" that should be localized.

🔎 Proposed localization fix
 		createValidatedInput({
 			app: this.app,
 			parent: this.contentEl,
 			initialValue: this.choice.templatePath,
 			placeholder: t("builder.template.path"),
 			suggestions: templates,
 			maxSuggestions: 50,
 			validator: (raw) => {
 				const v = raw.trim();
 				if (!v) return true;
-				return templates.includes(v) || "Template not found";
+				return templates.includes(v) || t("builder.common.template_not_found");
 			},
 			onChange: (value) => {

196-209: Incomplete localization: description remains hardcoded.

Lines 199-200 contain a hardcoded description that should be localized.

🔎 Proposed localization fix
 			stn
 				.setName(t("builder.template.include_subfolders"))
 				.setDesc(
-					"Get prompted to choose from both the selected folders and their subfolders when creating the note.",
+					t("builder.template.include_subfolders_desc"),
 				)
 				.addToggle((toggle) =>

213-229: Incomplete localization: description remains hardcoded.

Lines 218-219 contain a hardcoded description that should be localized.

🔎 Proposed localization fix
 			createInSameFolderAsActiveFileSetting
 				.setName(t("builder.template.same_folder"))
 				.setDesc(
-					"Creates the file in the same folder as the currently active file. Will not create the file if there is no active file.",
+					t("builder.template.same_folder_desc"),
 				)
 				.addToggle((toggle) =>

256-262: Incomplete localization: placeholder remains hardcoded.

Line 261 contains a hardcoded placeholder "Folder path" that should be localized.

🔎 Proposed localization fix
 		const folderInput = new TextComponent(inputContainer);
 		folderInput.inputEl.style.width = "100%";
-		folderInput.setPlaceholder("Folder path");
+		folderInput.setPlaceholder(t("builder.template.folder_path"));
 		const allFolders: string[] = getAllFolderPathsInVault(this.app);

293-299: Incomplete localization: button text remains hardcoded.

Line 296 contains hardcoded button text "Add" that should be localized.

🔎 Proposed localization fix
 		const addButton: ButtonComponent = new ButtonComponent(inputContainer);
 		addButton
 			.setCta()
-			.setButtonText("Add")
+			.setButtonText(t("builder.common.add"))
 			.onClick((evt) => {

354-387: Incomplete localization: description remains hardcoded.

Line 357 contains a hardcoded description "Where to place the link when appending" that should be localized.

🔎 Proposed localization fix
 			placementSetting
 				.setName(t("builder.append_link.placement"))
-				.setDesc("Where to place the link when appending")
+				.setDesc(t("builder.append_link.placement_desc"))
 				.addDropdown((dropdown) => {

389-417: Incomplete localization: description remains hardcoded.

Line 393 contains a hardcoded description that should be localized.

🔎 Proposed localization fix
 				linkTypeSetting
 					.setName(t("builder.append_link.type"))
-					.setDesc("Choose whether replacing the selection should insert a link or an embed.")
+					.setDesc(t("builder.append_link.type_desc"))
 					.addDropdown((dropdown) => {
🧹 Nitpick comments (5)
src/i18n/i18n.ts (2)

5-5: Consider adding type safety to the locales object.

Using any loses type checking benefits. Consider defining a recursive type for the locale structure.

🔎 Proposed type improvement
+type LocaleValue = string | { [key: string]: LocaleValue };
+type Locale = { [key: string]: LocaleValue };
+
-const locales: { [key: string]: any } = { en, fr };
+const locales: { [key: string]: Locale } = { en, fr };

7-14: Potential locale matching issue with regional variants.

moment.locale() may return regional variants like "en-US" or "fr-FR" which won't match the "en" or "fr" keys. Consider extracting the base language code.

🔎 Proposed fix for locale matching
 export function t(path: string): string {
     const lang = moment.locale();
-    const locale = locales[lang] || locales.en;
+    const baseLang = lang.split('-')[0]; // Handle "en-US" -> "en"
+    const locale = locales[lang] || locales[baseLang] || locales.en;
     
-    // Permet d'accéder aux objets imbriqués (ex: "settings.headers.choices")
+    // Access nested objects (e.g., "settings.headers.choices")
     const value = path.split('.').reduce((obj, key) => obj?.[key], locale);
     
     return value || path;
 }

Also, the comment on line 11 is in French—consider keeping code comments in English for consistency with the codebase.

src/main.ts (1)

26-26: Remove the French comment.

The inline comment // Ajout de l'import should be removed or translated to English for codebase consistency.

🔎 Proposed fix
-import { t } from "./i18n/i18n"; // Ajout de l'import
+import { t } from "./i18n/i18n";
src/quickAddSettingsTab.ts (1)

142-142: Consider using DOM APIs instead of innerHTML for safer content insertion.

While the current values come from build-time constants and bundled locale files (low XSS risk), using DOM methods like createEl and textContent is a safer pattern that avoids static analysis warnings.

🔎 Proposed refactor using DOM APIs
 if (__DEV_GIT_BRANCH__ !== null) {
-    const branchDiv = infoContainer.createDiv();
-    branchDiv.innerHTML = `<strong>${t("settings.dev.branch")}:</strong> ${__DEV_GIT_BRANCH__}`;
-    branchDiv.style.marginBottom = "5px";
+    const branchDiv = infoContainer.createDiv();
+    const branchLabel = branchDiv.createEl("strong");
+    branchLabel.textContent = `${t("settings.dev.branch")}:`;
+    branchDiv.appendText(` ${__DEV_GIT_BRANCH__}`);
+    branchDiv.style.marginBottom = "5px";
 }

 if (__DEV_GIT_COMMIT__ !== null) {
-    const commitDiv = infoContainer.createDiv();
-    commitDiv.innerHTML = `<strong>${t("settings.dev.commit")}:</strong> ${__DEV_GIT_COMMIT__}`;
-    commitDiv.style.marginBottom = "5px";
+    const commitDiv = infoContainer.createDiv();
+    const commitLabel = commitDiv.createEl("strong");
+    commitLabel.textContent = `${t("settings.dev.commit")}:`;
+    commitDiv.appendText(` ${__DEV_GIT_COMMIT__}`);
+    commitDiv.style.marginBottom = "5px";
 }

 if (__DEV_GIT_DIRTY__ !== null) {
-    const statusDiv = infoContainer.createDiv();
-    const statusText = __DEV_GIT_DIRTY__
-        ? `${t("settings.dev.yes")} (${t("settings.dev.changes")})`
-        : t("settings.dev.no");
-    const statusColor = __DEV_GIT_DIRTY__
-        ? "var(--text-warning)"
-        : "var(--text-success)";
-    statusDiv.innerHTML = `<strong>${t("settings.dev.changes")}:</strong> <span style="color: ${statusColor}">${statusText}</span>`;
+    const statusDiv = infoContainer.createDiv();
+    const statusLabel = statusDiv.createEl("strong");
+    statusLabel.textContent = `${t("settings.dev.changes")}:`;
+    const statusText = __DEV_GIT_DIRTY__
+        ? `${t("settings.dev.yes")} (${t("settings.dev.changes")})`
+        : t("settings.dev.no");
+    const statusSpan = statusDiv.createEl("span");
+    statusSpan.textContent = ` ${statusText}`;
+    statusSpan.style.color = __DEV_GIT_DIRTY__
+        ? "var(--text-warning)"
+        : "var(--text-success)";
 }

Also applies to: 148-148, 160-160

src/gui/ChoiceBuilder/captureChoiceBuilder.ts (1)

693-705: Incomplete localization: tooltip remains hardcoded.

Line 699 contains a hardcoded tooltip that should be localized.

🔎 Proposed localization fix
 			.addToggle((toggle) =>
 				toggle
 					.setValue(this.choice?.createFileIfItDoesntExist?.enabled)
-					.setTooltip("Create file if it doesn't exist")
+					.setTooltip(t("builder.capture.create_if_missing_tooltip"))
 					.onChange((value) => {
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 786b53c and 6ca0f37.

📒 Files selected for processing (10)
  • src/gui/ChoiceBuilder/captureChoiceBuilder.ts
  • src/gui/ChoiceBuilder/choiceBuilder.ts
  • src/gui/ChoiceBuilder/templateChoiceBuilder.ts
  • src/gui/MacroGUIs/MacroBuilder.ts
  • src/i18n/i18n.ts
  • src/i18n/locales/en.json
  • src/i18n/locales/fr.json
  • src/main.ts
  • src/quickAddSettingsTab.ts
  • tsconfig.json
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx,mts,mjs,js,json}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,mts,mjs,js,json}: Use tab indentation with width 2 in TypeScript and configuration files (enforced by Biome).
Follow an 80-character line guide (enforced by Biome).

Files:

  • src/i18n/i18n.ts
  • src/gui/ChoiceBuilder/captureChoiceBuilder.ts
  • src/main.ts
  • src/gui/ChoiceBuilder/templateChoiceBuilder.ts
  • src/gui/MacroGUIs/MacroBuilder.ts
  • src/i18n/locales/en.json
  • tsconfig.json
  • src/gui/ChoiceBuilder/choiceBuilder.ts
  • src/i18n/locales/fr.json
  • src/quickAddSettingsTab.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use camelCase for variables and functions in TypeScript.
Prefer type-only imports in TypeScript.

Files:

  • src/i18n/i18n.ts
  • src/gui/ChoiceBuilder/captureChoiceBuilder.ts
  • src/main.ts
  • src/gui/ChoiceBuilder/templateChoiceBuilder.ts
  • src/gui/MacroGUIs/MacroBuilder.ts
  • src/gui/ChoiceBuilder/choiceBuilder.ts
  • src/quickAddSettingsTab.ts
**/*.{ts,tsx,svelte}

📄 CodeRabbit inference engine (AGENTS.md)

Use PascalCase for classes and Svelte components.

Files:

  • src/i18n/i18n.ts
  • src/gui/ChoiceBuilder/captureChoiceBuilder.ts
  • src/main.ts
  • src/gui/ChoiceBuilder/templateChoiceBuilder.ts
  • src/gui/MacroGUIs/MacroBuilder.ts
  • src/gui/ChoiceBuilder/choiceBuilder.ts
  • src/quickAddSettingsTab.ts
src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Route logging through the logger utilities for consistent output.

Files:

  • src/i18n/i18n.ts
  • src/gui/ChoiceBuilder/captureChoiceBuilder.ts
  • src/main.ts
  • src/gui/ChoiceBuilder/templateChoiceBuilder.ts
  • src/gui/MacroGUIs/MacroBuilder.ts
  • src/gui/ChoiceBuilder/choiceBuilder.ts
  • src/quickAddSettingsTab.ts
src/main.ts

📄 CodeRabbit inference engine (AGENTS.md)

Preserve the hand-ordered imports in src/main.ts; disable auto-sorting there.

Files:

  • src/main.ts
🧠 Learnings (8)
📚 Learning: 2025-12-21T07:54:34.875Z
Learnt from: CR
Repo: chhoumann/quickadd PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-21T07:54:34.875Z
Learning: Applies to src/{engine,services,utils}/**/*.{ts,tsx} : Structure production code so Obsidian dependencies are injected behind interfaces to enable unit testing without loading real Obsidian modules.

Applied to files:

  • src/i18n/i18n.ts
  • tsconfig.json
📚 Learning: 2025-12-21T07:54:34.875Z
Learnt from: CR
Repo: chhoumann/quickadd PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-21T07:54:34.875Z
Learning: Applies to **/*.{ts,tsx} : Prefer type-only imports in TypeScript.

Applied to files:

  • tsconfig.json
📚 Learning: 2025-12-21T07:54:34.875Z
Learnt from: CR
Repo: chhoumann/quickadd PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-21T07:54:34.875Z
Learning: Applies to **/*.{ts,tsx,mts,mjs,js,json} : Use tab indentation with width 2 in TypeScript and configuration files (enforced by Biome).

Applied to files:

  • tsconfig.json
📚 Learning: 2025-12-21T07:54:34.875Z
Learnt from: CR
Repo: chhoumann/quickadd PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-21T07:54:34.875Z
Learning: Applies to src/main.ts : Preserve the hand-ordered imports in `src/main.ts`; disable auto-sorting there.

Applied to files:

  • tsconfig.json
📚 Learning: 2025-12-21T07:54:34.875Z
Learnt from: CR
Repo: chhoumann/quickadd PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-21T07:54:34.875Z
Learning: Applies to **/*.{ts,tsx} : Use camelCase for variables and functions in TypeScript.

Applied to files:

  • tsconfig.json
📚 Learning: 2025-12-21T07:54:34.875Z
Learnt from: CR
Repo: chhoumann/quickadd PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-21T07:54:34.875Z
Learning: Applies to src/**/*.ts src/**/*.tsx src/**/*.svelte src/engine/** src/services/** src/utils/** src/gui/** src/types/** src/quickAddSettingsTab.ts tests/** docs/** : Source code lives in `src/`: core logic under `engine/`, `services/`, and `utils/`; Svelte UI in `src/gui`; shared types in `src/types`; settings entry in `src/quickAddSettingsTab.ts`. Bundled artifacts `main.js` and `styles.css` stay at the repo root and should be generated, not hand-edited. Place tests and stubs in `tests/`, and keep user-facing docs in `docs/`.

Applied to files:

  • tsconfig.json
📚 Learning: 2025-12-21T07:54:34.875Z
Learnt from: CR
Repo: chhoumann/quickadd PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-21T07:54:34.875Z
Learning: Applies to tests/**/*.{test,spec}.{ts,tsx} : Use Testing Library helpers for Svelte components.

Applied to files:

  • tsconfig.json
📚 Learning: 2025-12-21T07:54:34.875Z
Learnt from: CR
Repo: chhoumann/quickadd PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-21T07:54:34.875Z
Learning: Applies to **/*.{ts,tsx,svelte} : Use PascalCase for classes and Svelte components.

Applied to files:

  • tsconfig.json
🧬 Code graph analysis (6)
src/gui/ChoiceBuilder/captureChoiceBuilder.ts (2)
tests/obsidian-stub.ts (1)
  • Setting (151-233)
src/i18n/i18n.ts (1)
  • t (7-15)
src/main.ts (1)
src/i18n/i18n.ts (1)
  • t (7-15)
src/gui/ChoiceBuilder/templateChoiceBuilder.ts (3)
tests/obsidian-stub.ts (2)
  • Setting (151-233)
  • TextComponent (122-149)
src/i18n/i18n.ts (1)
  • t (7-15)
src/constants.ts (5)
  • fileExistsAppendToBottom (176-177)
  • fileExistsAppendToTop (178-178)
  • fileExistsIncrement (175-175)
  • fileExistsOverwriteFile (179-179)
  • fileExistsDoNothing (180-180)
src/gui/MacroGUIs/MacroBuilder.ts (1)
src/i18n/i18n.ts (1)
  • t (7-15)
src/gui/ChoiceBuilder/choiceBuilder.ts (1)
src/i18n/i18n.ts (1)
  • t (7-15)
src/quickAddSettingsTab.ts (2)
src/i18n/i18n.ts (1)
  • t (7-15)
src/settingsStore.ts (1)
  • settingsStore (8-21)
🪛 ast-grep (0.40.3)
src/quickAddSettingsTab.ts

[warning] 147-147: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: commitDiv.innerHTML = <strong>${t("settings.dev.commit")}:</strong> ${__DEV_GIT_COMMIT__}
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 159-159: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: statusDiv.innerHTML = <strong>${t("settings.dev.changes")}:</strong> <span style="color: ${statusColor}">${statusText}</span>
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 147-147: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: commitDiv.innerHTML = <strong>${t("settings.dev.commit")}:</strong> ${__DEV_GIT_COMMIT__}
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


[warning] 159-159: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: statusDiv.innerHTML = <strong>${t("settings.dev.changes")}:</strong> <span style="color: ${statusColor}">${statusText}</span>
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)

🔇 Additional comments (8)
tsconfig.json (1)

18-19: LGTM!

The addition of resolveJsonModule and esModuleInterop correctly enables JSON module imports required for the i18n locale files.

src/gui/MacroGUIs/MacroBuilder.ts (2)

16-16: LGTM!

The i18n import and integration follow the established pattern correctly.


87-87: LGTM!

All translation keys are properly structured and match the locale file definitions. The string interpolation pattern (e.g., `${t("macro.renaming")} ${this.choice.name}`) is appropriate for dynamic content.

Also applies to: 102-103, 157-157, 162-162

src/gui/ChoiceBuilder/choiceBuilder.ts (2)

8-8: LGTM!

The i18n import follows the established pattern.


40-46: LGTM!

All translation keys are properly structured and consistently applied across the choice builder settings. The key paths match the locale file definitions.

Also applies to: 62-62, 90-90, 113-113, 151-161, 172-177, 188-195, 210-211

src/i18n/locales/fr.json (1)

1-204: LGTM on overall structure.

The French translations are well-structured and comprehensive. The bilingual approach (including English terms in parentheses for technical concepts) is helpful for users.

src/quickAddSettingsTab.ts (2)

25-25: LGTM!

The i18n import follows the established pattern.


54-84: LGTM!

All section headers correctly use i18n translations with proper key paths.

// Permet d'accéder aux objets imbriqués (ex: "settings.headers.choices")
const value = path.split('.').reduce((obj, key) => obj?.[key], locale);

return value || path;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Empty string values will incorrectly fall back to the path.

The expression value || path returns path when value is an empty string "". If an empty string is a valid translation, use nullish coalescing instead.

🔎 Proposed fix
-    return value || path;
+    return value ?? path;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return value || path;
return value ?? path;
🤖 Prompt for AI Agents
In src/i18n/i18n.ts around line 14, the return expression uses `value || path`
which treats an empty string as falsy and incorrectly falls back to `path`;
replace the fallback with a nullish check (use `value ?? path`) so that
empty-string translations are preserved while still falling back when `value` is
null or undefined.

Comment on lines +24 to +27
"announce": {
"name": "Announce Updates",
"desc": "Display release notes when a new version is installed."
},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Missing translation keys for announce dropdown options.

Same issue as in fr.json - the keys settings.announce.all, settings.announce.major, and settings.announce.none are missing but used in the settings tab.

🔎 Proposed fix - Add missing keys
   "announce": {
     "name": "Announce Updates",
-    "desc": "Display release notes when a new version is installed."
+    "desc": "Display release notes when a new version is installed.",
+    "all": "All updates",
+    "major": "Major updates only",
+    "none": "Never"
   },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"announce": {
"name": "Announce Updates",
"desc": "Display release notes when a new version is installed."
},
"announce": {
"name": "Announce Updates",
"desc": "Display release notes when a new version is installed.",
"all": "All updates",
"major": "Major updates only",
"none": "Never"
},
🤖 Prompt for AI Agents
In src/i18n/locales/en.json around lines 24 to 27, the announce object is
missing translation keys used by the settings dropdown; add keys
"settings.announce.all", "settings.announce.major", and "settings.announce.none"
(with appropriate English strings like "All updates", "Major updates only",
"None") under the announce object so the settings tab can display the dropdown
options.

Comment on lines +24 to +27
"announce": {
"name": "Annoncer les mises à jour",
"desc": "Affiche les notes de version lors de l'installation d'une nouvelle version."
},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Missing translation keys for announce dropdown options.

The settings tab uses t("settings.announce.all"), t("settings.announce.major"), and t("settings.announce.none") (see quickAddSettingsTab.ts lines 254-256), but these keys are missing from both locale files. Only name and desc are defined.

🔎 Proposed fix - Add missing keys
   "announce": {
     "name": "Annoncer les mises à jour",
-    "desc": "Affiche les notes de version lors de l'installation d'une nouvelle version."
+    "desc": "Affiche les notes de version lors de l'installation d'une nouvelle version.",
+    "all": "Toutes les mises à jour",
+    "major": "Mises à jour majeures uniquement",
+    "none": "Jamais"
   },
🤖 Prompt for AI Agents
In src/i18n/locales/fr.json around lines 24 to 27, the announce object only
contains "name" and "desc" but the UI expects keys "all", "major", and "none";
add these missing keys under "announce" with appropriate French translations
(e.g., "all": "Toutes les mises à jour", "major": "Mises à jour majeures
uniquement", "none": "Ne pas afficher") so t("settings.announce.all"),
t("settings.announce.major") and t("settings.announce.none") resolve correctly.

Comment on lines +254 to +256
.addOption("all", t("settings.announce.all"))
.addOption("major", t("settings.announce.major"))
.addOption("none", t("settings.announce.none"))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

These translation keys don't exist in the locale files.

t("settings.announce.all"), t("settings.announce.major"), and t("settings.announce.none") will return the raw paths since these keys are missing from en.json and fr.json.

🤖 Prompt for AI Agents
In src/quickAddSettingsTab.ts around lines 254 to 256, the three translation
lookups t("settings.announce.all"), t("settings.announce.major") and
t("settings.announce.none") point to keys that don't exist in the locale files;
add these keys to both en.json and fr.json under the "settings" -> "announce"
object (e.g. "all", "major", "none") with appropriate English and French
strings, or alternatively change the t(...) calls to use existing locale keys —
ensure both locale files are updated so the calls return translated strings
rather than raw key paths.

Comment on lines +488 to +489
.setName(t("settings.ribbon_options.name"))
.setDesc(t("settings.ribbon_options.desc"))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Key path mismatch with locale files.

The code uses settings.ribbon_options.name and settings.ribbon_options.desc, but the locale files define these under settings.ribbon. Either update the code to use settings.ribbon.* or rename the keys in the locale files to ribbon_options.

🤖 Prompt for AI Agents
In src/quickAddSettingsTab.ts around lines 488-489, the i18n keys used are
settings.ribbon_options.name and settings.ribbon_options.desc but the locale
files define them under settings.ribbon; update the code to reference
settings.ribbon.name and settings.ribbon.desc (or alternatively rename the keys
in the locale files to settings.ribbon_options.*) so the key paths match; ensure
both name and desc use the same base key and run a quick i18n lookup test to
confirm translations load.

Copy link
Author

@Amato21 Amato21 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have addressed all the feedback from CodeRabbit bot:

  • Localization: Added the missing ribbon.icon key to en.json and fr.json.
  • Capture Builder: Replaced all remaining hardcoded English strings in captureChoiceBuilder.ts with localization keys (t(...)).
  • Security: Replaced innerHTML usage in quickAddSettingsTab.ts with safe DOM creation methods (createEl, textContent) to prevent potential XSS warnings.

The code should now be clean and fully localized!

@Amato21 Amato21 changed the title Add i18n support & French translation (Settings, Builders, Macros) feat: Add i18n support & French translation (Settings, Builders, Macros) Dec 29, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant