Skip to content

Commit

Permalink
commands
Browse files Browse the repository at this point in the history
  • Loading branch information
pontusab committed Jan 19, 2025
1 parent cf66a36 commit c0f5826
Show file tree
Hide file tree
Showing 7 changed files with 203 additions and 56 deletions.
2 changes: 1 addition & 1 deletion apps/web/languine.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { defineConfig } from "languine";
export default defineConfig({
locale: {
source: "en",
targets: ["es", "sv", "de", "fr"],
targets: ["es", "sv", "de", "fr", "fi", "pt"],
},
files: {
ts: {
Expand Down
2 changes: 1 addition & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"@radix-ui/react-tooltip": "^1.1.6",
"@react-email/components": "0.0.32",
"@react-email/font": "^0.0.9",
"@tanstack/react-query": "^5.64.1",
"@tanstack/react-query": "^5.64.2",
"@trigger.dev/sdk": "3.3.11",
"@trpc/client": "11.0.0-rc.700",
"@trpc/react-query": "11.0.0-rc.700",
Expand Down
Binary file modified bun.lockb
Binary file not shown.
81 changes: 33 additions & 48 deletions packages/cli/src/commands/init.ts
Original file line number Diff line number Diff line change
@@ -1,63 +1,50 @@
import { existsSync } from "node:fs";
import { intro, isCancel, outro, select, text } from "@clack/prompts";
import chalk from "chalk";
import type { parserTypeSchema } from "../parsers/index.js";
import type { Config } from "../types.js";
import { loadSession } from "../utils/session.js";
import { commands as authCommands } from "./auth/index.js";

type Format =
| "json"
| "yaml"
| "properties"
| "android"
| "ios-strings"
| "ios-stringsdict"
| "md"
| "html"
| "txt"
| "ts"
| "po"
| "xliff"
| "csv"
| "resx"
| "arb";
type Format = typeof parserTypeSchema._type;

const SUPPORTED_FORMATS = [
{ value: "json" as const, label: "JSON (.json)" },
{ value: "yaml" as const, label: "YAML (.yml, .yaml)" },
{ value: "properties" as const, label: "Java Properties (.properties)" },
{ value: "android" as const, label: "Android XML (.xml)" },
{ value: "ios-strings" as const, label: "iOS Strings (.strings)" },
{
value: "ios-stringsdict" as const,
label: "iOS Stringsdict (.stringsdict)",
},
{ value: "md" as const, label: "Markdown (.md)" },
{ value: "html" as const, label: "HTML (.html)" },
{ value: "txt" as const, label: "Text (.txt)" },
{ value: "ts" as const, label: "TypeScript (.ts)" },
{ value: "po" as const, label: "Gettext PO (.po)" },
{ value: "xliff" as const, label: "XLIFF (.xlf, .xliff)" },
{ value: "csv" as const, label: "CSV (.csv)" },
{ value: "resx" as const, label: ".NET RESX (.resx)" },
{ value: "arb" as const, label: "Flutter ARB (.arb)" },
];
{ value: "json", label: "JSON (.json)" },
{ value: "yml", label: "YAML (.yml, .yaml)" },
{ value: "properties", label: "Java Properties (.properties)" },
{ value: "android-xml", label: "Android XML (.xml)" },
{ value: "xcode-strings", label: "iOS Strings (.strings)" },
{ value: "xcode-stringsdict", label: "iOS Stringsdict (.stringsdict)" },
{ value: "xcode-xcstrings", label: "iOS XCStrings (.xcstrings)" },
{ value: "md", label: "Markdown (.md)" },
{ value: "mdx", label: "MDX (.mdx)" },
{ value: "html", label: "HTML (.html)" },
{ value: "js", label: "JavaScript (.js)" },
{ value: "ts", label: "TypeScript (.ts)" },
{ value: "po", label: "Gettext PO (.po)" },
{ value: "xliff", label: "XLIFF (.xlf, .xliff)" },
{ value: "csv", label: "CSV (.csv)" },
{ value: "xml", label: "XML (.xml)" },
{ value: "arb", label: "Flutter ARB (.arb)" },
] as const;

const FORMAT_EXAMPLES: Record<Format, string> = {
json: "src/locales/[locale].json",
yaml: "src/locales/[locale].yaml",
yml: "src/locales/[locale].yaml",
properties: "src/locales/messages_[locale].properties",
android: "res/values-[locale]/strings.xml",
"ios-strings": "[locale].lproj/Localizable.strings",
"ios-stringsdict": "[locale].lproj/Localizable.stringsdict",
"android-xml": "res/values-[locale]/strings.xml",
"xcode-strings": "[locale].lproj/Localizable.strings",
"xcode-stringsdict": "[locale].lproj/Localizable.stringsdict",
"xcode-xcstrings": "[locale].lproj/Localizable.xcstrings",
md: "src/docs/[locale]/*.md",
mdx: "src/docs/[locale]/*.mdx",
html: "src/content/[locale]/**/*.html",
txt: "src/content/[locale]/**/*.txt",
js: "src/locales/[locale].js",
ts: "src/locales/[locale].ts",
po: "src/locales/[locale].po",
xliff: "src/locales/[locale].xlf",
csv: "src/locales/[locale].csv",
resx: "src/locales/[locale].resx",
xml: "src/locales/[locale].xml",
arb: "lib/l10n/app_[locale].arb",
};

Expand Down Expand Up @@ -115,23 +102,21 @@ export async function commands() {
const fileConfigs: Config["files"] = {};

// Select format
const format = await select({
const format = (await select({
message: "Select file format",
options: SUPPORTED_FORMATS,
});
options: [...SUPPORTED_FORMATS],
})) as Format;

if (isCancel(format)) {
outro("Configuration cancelled");
process.exit(0);
}

const formatKey = format as keyof typeof FORMAT_EXAMPLES;

// Get file pattern
const pattern = await text({
message: "Enter the file pattern for translations",
placeholder: FORMAT_EXAMPLES[formatKey],
defaultValue: FORMAT_EXAMPLES[formatKey],
placeholder: FORMAT_EXAMPLES[format],
defaultValue: FORMAT_EXAMPLES[format],
validate(value) {
if (!value) return;

Expand Down
118 changes: 118 additions & 0 deletions packages/cli/src/commands/locale.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { readFile, writeFile } from "node:fs/promises";
import { loadConfig } from "@/utils/config.ts";
import { configFile } from "@/utils/config.ts";
import { outro, spinner } from "@clack/prompts";
import chalk from "chalk";
import { z } from "zod";

const argsSchema = z.array(z.string()).transform((args) => {
const [command, ...locales] = args;
const localeList = locales
.join("")
.split(",")
.map((l) => l.trim());

return {
command,
locales: localeList,
};
});

export async function localeCommand(args: string[] = []) {
const { command, locales } = argsSchema.parse(args);
const s = spinner();

if (!command || !["add", "remove"].includes(command)) {
throw new Error("Please specify a command: add or remove");
}

if (locales.length === 0) {
throw new Error("Please specify at least one locale code");
}

try {
// Load config file
const config = await loadConfig();
const { path: configPath } = await configFile();

if (!config) {
throw new Error(
"Configuration file not found. Please run `languine init` to create one.",
);
}

s.start(command === "add" ? "Adding locales..." : "Removing locales...");

// Read the current config file content
const configContent = await readFile(configPath, "utf-8");

let updatedContent = configContent;
const currentTargets = config.locale.targets;

if (command === "add") {
// Add new locales that don't already exist
const newLocales = locales.filter(
(locale) => !currentTargets.includes(locale),
);

if (newLocales.length === 0) {
s.stop("No new locales to add");
outro("All specified locales are already in the configuration.");
return;
}

const updatedTargets = [...currentTargets, ...newLocales];
updatedContent = updateTargetsInConfig(configContent, updatedTargets);

s.stop("Locales added successfully");
outro(`Added locales: ${newLocales.join(", ")}`);
} else {
// Remove specified locales
const localesToRemove = locales.filter((locale) =>
currentTargets.includes(locale),
);

if (localesToRemove.length === 0) {
s.stop("No locales to remove");
outro("None of the specified locales exist in the configuration.");
return;
}

const updatedTargets = currentTargets.filter(
(locale) => !localesToRemove.includes(locale),
);
updatedContent = updateTargetsInConfig(configContent, updatedTargets);

s.stop("Locales removed successfully");
outro(`Removed locales: ${localesToRemove.join(", ")}`);
}

// Write the updated config back to file
await writeFile(configPath, updatedContent, "utf-8");
} catch (error) {
const localeError = error as Error;
console.error(chalk.red(`Locale command failed: ${localeError.message}`));
process.exit(1);
}
}

function updateTargetsInConfig(
configContent: string,
targets: string[],
): string {
if (configContent.includes("targets:")) {
// For TypeScript config
if (configContent.includes("defineConfig")) {
return configContent.replace(
/targets:\s*\[(.*?)\]/s,
`targets: ["${targets.join('", "')}"]`,
);
}
// For JSON config
return configContent.replace(
/"targets"\s*:\s*\[(.*?)\]/s,
`"targets": ["${targets.join('", "')}"]`,
);
}
return configContent;
}
33 changes: 27 additions & 6 deletions packages/cli/src/commands/run.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
import { commands as authCommands } from "@/commands/auth/index.ts";
import { commands as initCommands } from "@/commands/init.ts";
import { localeCommand } from "@/commands/locale.ts";
import { syncCommand } from "@/commands/sync.ts";
import { translateCommand } from "@/commands/translate.ts";
import { isGitRepo } from "@/utils/git.ts";
import { isCancel, select } from "@clack/prompts";
import chalk from "chalk";

export async function runCommands() {
const [mainCommand, subCommand, ...args] = process.argv.slice(2);

if (!isGitRepo()) {
console.error(
chalk.red(
"This command must be run from within a git repository. Please initialize git first.",
),
);
process.exit(1);
}

if (mainCommand) {
switch (mainCommand) {
case "auth":
Expand All @@ -23,6 +35,10 @@ export async function runCommands() {
await syncCommand([...args, subCommand].filter(Boolean));
break;
}
case "locale": {
await localeCommand([subCommand, ...args].filter(Boolean));
break;
}
default:
process.exit(1);
}
Expand All @@ -39,6 +55,10 @@ export async function runCommands() {
value: "sync",
label: "Sync deleted keys between source and target files",
},
{
value: "locale",
label: "Manage target locales",
},
],
});

Expand All @@ -53,13 +73,14 @@ export async function runCommands() {
case "init":
await initCommands();
break;
case "translate": {
await translateCommand([...args, subCommand].filter(Boolean));
case "translate":
await translateCommand();
break;
}
case "sync": {
await syncCommand([...args, subCommand].filter(Boolean));
case "sync":
await syncCommand();
break;
case "locale":
await localeCommand();
break;
}
}
}
23 changes: 23 additions & 0 deletions packages/cli/src/utils/git.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { existsSync } from "node:fs";
import { dirname, join } from "node:path";

/**
* Check if the current directory is within a git repository
* Traverses up the directory tree until it finds a .git directory or reaches the root
* @returns {boolean} True if within a git repository
*/
export function isGitRepo(startPath: string = process.cwd()): boolean {
const gitDir = join(startPath, ".git");

if (existsSync(gitDir)) {
return true;
}

const parentDir = dirname(startPath);
// If we've reached the root directory
if (parentDir === startPath) {
return false;
}

return isGitRepo(parentDir);
}

0 comments on commit c0f5826

Please sign in to comment.