diff --git a/apps/web/src/components/commands.tsx b/apps/web/src/components/commands.tsx
index 551386f..93f961a 100644
--- a/apps/web/src/components/commands.tsx
+++ b/apps/web/src/components/commands.tsx
@@ -103,7 +103,7 @@ export function Commands() {
step >= 2 ? "opacity-100" : "opacity-0",
)}
>
- │ es,pt,fr
+ │ es, pt, fr
│ ○ JSON (.json)
- = 4 ? "opacity-100" : "opacity-0",
- )}
- >
- │ ○ YAML (.yaml)
-
- = 4 ? "opacity-100" : "opacity-0",
- )}
- >
- │ ○ Markdown (.md)
-
│ ○ GPT-3.5 Turbo
- = 5 ? "opacity-100" : "opacity-0",
- )}
- >
- │
-
-
- = 6 ? "opacity-100" : "opacity-0",
- )}
- >
- ◆ Which OpenAI model should be used for translations?
-
- = 6 ? "opacity-100" : "opacity-0",
- )}
- >
- │ GPT-4 (Default)
-
= 7 ? "opacity-100" : "opacity-0",
+ "transition-opacity duration-100 -ml-[1.5px]",
+ step >= 6 ? "opacity-100" : "opacity-0",
)}
>
└ Configuration file and language files created successfully!
diff --git a/examples/i18next/locales/en.json b/examples/i18next/locales/en.json
index cfbcd21..34b25ba 100644
--- a/examples/i18next/locales/en.json
+++ b/examples/i18next/locales/en.json
@@ -5,6 +5,5 @@
},
"interpolated": "Have a nice day, {{name}}!",
"pluralKey_one": "This is a nice example.",
- "pluralKey_other": "This are nice examples.",
- "missing_translation": "This should work"
+ "pluralKey_other": "This are nice examples."
}
diff --git a/examples/i18next/locales/sv.json b/examples/i18next/locales/sv.json
index 5e4259e..c878c82 100644
--- a/examples/i18next/locales/sv.json
+++ b/examples/i18next/locales/sv.json
@@ -5,6 +5,5 @@
},
"interpolated": "Ha en trevlig dag, {{name}}!",
"pluralKey_one": "Det här är ett fint exempel.",
- "pluralKey_other": "Det här är fina exempel.",
- "missing_translation": "Detta bör fungera"
+ "pluralKey_other": "Det här är fina exempel."
}
\ No newline at end of file
diff --git a/examples/next-international/locales/en.ts b/examples/next-international/locales/en.ts
index 76ac4f9..a58ae73 100644
--- a/examples/next-international/locales/en.ts
+++ b/examples/next-international/locales/en.ts
@@ -10,5 +10,4 @@ export default {
"scope.more.stars#other": "{count} stars on GitHub",
"missing.translation.in.fr": "This should work",
"cows#one": "A cow",
- "cows#other": "{count} cows",
} as const;
diff --git a/examples/next-international/locales/fr.ts b/examples/next-international/locales/fr.ts
index aecd861..f2deaaa 100644
--- a/examples/next-international/locales/fr.ts
+++ b/examples/next-international/locales/fr.ts
@@ -9,7 +9,5 @@ export default {
"scope.more.stars#one": "1 étoile sur GitHub",
"scope.more.stars#other": "{count} étoiles sur GitHub",
"missing.translation.in.fr": "Cela devrait fonctionner",
- "cows#one": "Une vache",
- "cows#other": "{count} vaches",
- "hello.world2": "Bonjour le monde !"
+ "cows#one": "Une vache"
} as const;
diff --git a/packages/cli/src/commands/clean.ts b/packages/cli/src/commands/clean.ts
new file mode 100644
index 0000000..90addc0
--- /dev/null
+++ b/packages/cli/src/commands/clean.ts
@@ -0,0 +1,99 @@
+import fs from "node:fs/promises";
+import path from "node:path";
+import { intro, outro, spinner } from "@clack/prompts";
+import chalk from "chalk";
+import { getConfig } from "../utils.js";
+
+export async function clean() {
+ intro("Cleaning unused translations...");
+
+ try {
+ const config = await getConfig();
+ const s = spinner();
+ s.start("Removing unused translation keys...");
+
+ const { source, targets } = config.locale;
+ let totalKeysRemoved = 0;
+
+ // Process each file format and pattern
+ for (const [format, { include }] of Object.entries(config.files)) {
+ for (const pattern of include) {
+ const sourcePath = pattern.replace("[locale]", source);
+
+ // Read source file to get reference keys
+ const sourceContent = await fs.readFile(
+ path.join(process.cwd(), sourcePath),
+ "utf-8",
+ );
+
+ const sourceKeys =
+ format === "ts"
+ ? Object.keys(
+ Function(
+ `return ${sourceContent.replace(/export default |as const;/g, "")}`,
+ )(),
+ )
+ : Object.keys(JSON.parse(sourceContent));
+
+ // Clean each target locale file
+ for (const locale of targets) {
+ const targetPath = pattern.replace("[locale]", locale);
+
+ try {
+ const targetContent = await fs.readFile(
+ path.join(process.cwd(), targetPath),
+ "utf-8",
+ );
+
+ const targetObj =
+ format === "ts"
+ ? Function(
+ `return ${targetContent.replace(/export default |as const;/g, "")}`,
+ )()
+ : JSON.parse(targetContent);
+
+ const targetKeys = Object.keys(targetObj);
+ const keysToRemove = targetKeys.filter(
+ (key) => !sourceKeys.includes(key),
+ );
+ totalKeysRemoved += keysToRemove.length;
+
+ // Remove keys not in source
+ const cleanedObj = Object.fromEntries(
+ Object.entries(targetObj).filter(([key]) =>
+ sourceKeys.includes(key),
+ ),
+ );
+
+ // Format and write cleaned content
+ const finalContent =
+ format === "ts"
+ ? `export default ${JSON.stringify(cleanedObj, null, 2)} as const;\n`
+ : JSON.stringify(cleanedObj, null, 2);
+
+ await fs.writeFile(
+ path.join(process.cwd(), targetPath),
+ finalContent,
+ "utf-8",
+ );
+ } catch (error) {
+ console.error(chalk.red(`Error cleaning ${targetPath}:`), error);
+ }
+ }
+ }
+ }
+
+ s.stop("Cleaning completed");
+ outro(
+ totalKeysRemoved > 0
+ ? chalk.green(
+ `Successfully removed ${totalKeysRemoved} unused translation keys!`,
+ )
+ : chalk.green("No unused translations found!"),
+ );
+ } catch (error) {
+ outro(chalk.red("Failed to clean translations"));
+ console.error(error);
+ process.exit(1);
+ }
+}
diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts
index f0274f2..0f53a8c 100644
--- a/packages/cli/src/index.ts
+++ b/packages/cli/src/index.ts
@@ -6,6 +6,7 @@ dotenv.config();
import { select } from "@clack/prompts";
import chalk from "chalk";
import dedent from "dedent";
+import { clean } from "./commands/clean.js";
import { diff } from "./commands/diff.js";
import { init } from "./commands/init.js";
import { instructions } from "./commands/instructions.js";
@@ -41,6 +42,8 @@ const command =
{ value: "translate", label: "Translate to target languages" },
{ value: "instructions", label: "Add custom translation instructions" },
{ value: "diff", label: "Check for changes in source locale file" },
+ { value: "clean", label: "Clean unused translations" },
+ { value: "available", label: "Available commands" },
],
}));
@@ -54,14 +57,17 @@ if (command === "init") {
instructions();
} else if (command === "diff") {
diff();
-} else {
- console.log(chalk.bold("\nAvailable commands:"));
+} else if (command === "clean") {
+ clean();
+} else if (command === "available") {
console.log(dedent`
${chalk.cyan("init")} Initialize a new Languine configuration
${chalk.cyan("translate")} Translate to all target locales
${chalk.cyan("translate")} ${chalk.gray("")} Translate to a specific locale
${chalk.cyan("instructions")} Add custom translation instructions
-
+ ${chalk.cyan("diff")} Check for changes in source locale file
+ ${chalk.cyan("clean")} Clean unused translations
+ ${chalk.cyan("available")} Show available commands
Run ${chalk.cyan("languine ")} to execute a command
`);
}
diff --git a/packages/cli/src/prompt.ts b/packages/cli/src/prompt.ts
index 7d9c49e..e2265a8 100644
--- a/packages/cli/src/prompt.ts
+++ b/packages/cli/src/prompt.ts
@@ -12,4 +12,5 @@ Translation Requirements:
- Respect existing whitespace and newline patterns
- Keep all technical identifiers unchanged
- Translate only user-facing strings
+- Never add space before a ! or ?
`;