@lunar-kit/cli 0.1.3 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +232 -31
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { Command } from "commander";
5
- import chalk7 from "chalk";
5
+ import chalk8 from "chalk";
6
6
 
7
7
  // src/commands/init.ts
8
8
  import fs from "fs-extra";
@@ -959,33 +959,225 @@ export function ${exportName}({}: ${exportName}Props) {
959
959
  }
960
960
  }
961
961
 
962
+ // src/commands/localization.ts
963
+ import fs9 from "fs-extra";
964
+ import path8 from "path";
965
+ import chalk7 from "chalk";
966
+ import ora6 from "ora";
967
+ import prompts3 from "prompts";
968
+ async function generateLocale(lang) {
969
+ const spinner = ora6(`Generating locale: ${lang}...`).start();
970
+ try {
971
+ const config = await loadConfig();
972
+ if (!config) {
973
+ spinner.fail("kit.config.json not found. Run `lunar init` first.");
974
+ return;
975
+ }
976
+ const localesDir = path8.join(process.cwd(), config.localization?.localesDir || "src/locales");
977
+ await fs9.ensureDir(localesDir);
978
+ const targetFile = path8.join(localesDir, `${lang}.ts`);
979
+ if (fs9.existsSync(targetFile)) {
980
+ spinner.fail(`Locale "${lang}" already exists at ${targetFile}`);
981
+ return;
982
+ }
983
+ const existingLocales = (await fs9.readdir(localesDir)).filter((f) => f.endsWith(".ts") && f !== "index.ts" && f !== "locale-store.ts").map((f) => f.replace(".ts", ""));
984
+ let template;
985
+ if (existingLocales.length > 0) {
986
+ const sourceFile = path8.join(localesDir, `${existingLocales[0]}.ts`);
987
+ const sourceContent = await fs9.readFile(sourceFile, "utf-8");
988
+ template = sourceContent.replace(/from '\.\/index'/, `from './index'`).replace(
989
+ new RegExp(`const ${existingLocales[0]}`),
990
+ `const ${lang}`
991
+ ).replace(
992
+ new RegExp(`export default ${existingLocales[0]}`),
993
+ `export default ${lang}`
994
+ ).replace(/: '([^']+)'/g, (match, value) => {
995
+ if (value.includes("{{")) {
996
+ return `: '${value}'`;
997
+ }
998
+ return `: ''`;
999
+ });
1000
+ } else {
1001
+ template = `import type { TranslationDictionary } from './index';
1002
+
1003
+ const ${lang}: TranslationDictionary = {
1004
+ common: {
1005
+ ok: '',
1006
+ cancel: '',
1007
+ },
1008
+ button: {
1009
+ submit: '',
1010
+ },
1011
+ message: {
1012
+ welcome: '',
1013
+ },
1014
+ label: {},
1015
+ error: {},
1016
+ };
1017
+
1018
+ export default ${lang};
1019
+ `;
1020
+ }
1021
+ await fs9.writeFile(targetFile, template, "utf-8");
1022
+ await registerLocaleInIndex(localesDir, lang);
1023
+ if (config.localization) {
1024
+ if (!config.localization.locales.includes(lang)) {
1025
+ config.localization.locales.push(lang);
1026
+ const configPath = path8.join(process.cwd(), "kit.config.json");
1027
+ await fs9.writeJson(configPath, config, { spaces: 2 });
1028
+ }
1029
+ }
1030
+ spinner.succeed(chalk7.green(`Locale "${lang}" generated`));
1031
+ console.log(chalk7.cyan(` ${localesDir}/${lang}.ts`));
1032
+ console.log(chalk7.dim(` \u2192 Fill in the translations, then use t('category.key') in your components`));
1033
+ } catch (error) {
1034
+ spinner.fail("Failed to generate locale");
1035
+ console.error(error);
1036
+ }
1037
+ }
1038
+ async function addLocaleEntry() {
1039
+ try {
1040
+ const config = await loadConfig();
1041
+ if (!config) {
1042
+ console.log(chalk7.red("kit.config.json not found. Run `lunar init` first."));
1043
+ return;
1044
+ }
1045
+ const localesDir = path8.join(process.cwd(), config.localization?.localesDir || "src/locales");
1046
+ if (!fs9.existsSync(localesDir)) {
1047
+ console.log(chalk7.red("Locales directory not found. Initialize localization first."));
1048
+ return;
1049
+ }
1050
+ const localeFiles = (await fs9.readdir(localesDir)).filter((f) => f.endsWith(".ts") && f !== "index.ts" && f !== "locale-store.ts");
1051
+ if (localeFiles.length === 0) {
1052
+ console.log(chalk7.red("No locale files found. Run `lunar g locale <lang>` first."));
1053
+ return;
1054
+ }
1055
+ const locales = localeFiles.map((f) => f.replace(".ts", ""));
1056
+ const firstLocaleContent = await fs9.readFile(path8.join(localesDir, localeFiles[0]), "utf-8");
1057
+ const categoryMatches = firstLocaleContent.match(/^\s{2}(\w+):\s*\{/gm);
1058
+ const existingCategories = categoryMatches ? categoryMatches.map((m) => m.trim().replace(/:\s*\{$/, "")) : [];
1059
+ const categoryResponse = await prompts3({
1060
+ type: "select",
1061
+ name: "category",
1062
+ message: "Category:",
1063
+ choices: [
1064
+ ...existingCategories.map((c) => ({ title: c, value: c })),
1065
+ { title: chalk7.green("+ Create new category"), value: "__new__" }
1066
+ ]
1067
+ });
1068
+ let category = categoryResponse.category;
1069
+ if (!category) return;
1070
+ if (category === "__new__") {
1071
+ const newCatResponse = await prompts3({
1072
+ type: "text",
1073
+ name: "name",
1074
+ message: "New category name:",
1075
+ validate: (v) => /^[a-z_]+$/.test(v) || "Use lowercase with underscores"
1076
+ });
1077
+ category = newCatResponse.name;
1078
+ if (!category) return;
1079
+ }
1080
+ const keyResponse = await prompts3({
1081
+ type: "text",
1082
+ name: "key",
1083
+ message: "Key name:",
1084
+ validate: (v) => /^[a-z_]+$/.test(v) || "Use lowercase with underscores (e.g., hello_world)"
1085
+ });
1086
+ const key = keyResponse.key;
1087
+ if (!key) return;
1088
+ console.log(chalk7.dim("\nEnter translations for each language:"));
1089
+ const translations = {};
1090
+ for (const lang of locales) {
1091
+ const { value } = await prompts3({
1092
+ type: "text",
1093
+ name: "value",
1094
+ message: ` ${lang}`
1095
+ });
1096
+ if (value === void 0) return;
1097
+ translations[lang] = value;
1098
+ }
1099
+ const spinner = ora6("Adding translations...").start();
1100
+ for (const lang of locales) {
1101
+ const filePath = path8.join(localesDir, `${lang}.ts`);
1102
+ let content = await fs9.readFile(filePath, "utf-8");
1103
+ const escapedValue = translations[lang].replace(/'/g, "\\'");
1104
+ const categoryRegex = new RegExp(`(\\s{2}${category}:\\s*\\{)([^}]*)(\\})`);
1105
+ const categoryMatch = content.match(categoryRegex);
1106
+ if (categoryMatch) {
1107
+ const existingKeys = categoryMatch[2];
1108
+ const newKey = ` ${key}: '${escapedValue}',
1109
+ `;
1110
+ if (existingKeys.includes(`${key}:`)) {
1111
+ spinner.warn(chalk7.yellow(`Key "${category}.${key}" already exists in ${lang}.ts \u2014 skipping`));
1112
+ continue;
1113
+ }
1114
+ content = content.replace(
1115
+ categoryRegex,
1116
+ `$1${existingKeys}${newKey} $3`
1117
+ );
1118
+ } else {
1119
+ const newCategory = ` ${category}: {
1120
+ ${key}: '${escapedValue}',
1121
+ },
1122
+ `;
1123
+ content = content.replace(/^(\};)/m, `${newCategory}$1`);
1124
+ }
1125
+ await fs9.writeFile(filePath, content, "utf-8");
1126
+ }
1127
+ spinner.succeed(chalk7.green(`Added "${category}.${key}" to ${locales.length} locale files`));
1128
+ } catch (error) {
1129
+ console.error(chalk7.red("Failed to add locale entry"), error);
1130
+ }
1131
+ }
1132
+ async function registerLocaleInIndex(localesDir, lang) {
1133
+ const indexPath = path8.join(localesDir, "index.ts");
1134
+ if (!fs9.existsSync(indexPath)) return;
1135
+ let content = await fs9.readFile(indexPath, "utf-8");
1136
+ const registerLine = `registerLocale('${lang}', () => import('./${lang}'));`;
1137
+ if (content.includes(registerLine)) return;
1138
+ const lastRegisterIndex = content.lastIndexOf("registerLocale(");
1139
+ if (lastRegisterIndex !== -1) {
1140
+ const lineEnd = content.indexOf("\n", lastRegisterIndex);
1141
+ content = content.slice(0, lineEnd + 1) + registerLine + "\n" + content.slice(lineEnd + 1);
1142
+ } else {
1143
+ content += `
1144
+ ${registerLine}
1145
+ `;
1146
+ }
1147
+ await fs9.writeFile(indexPath, content, "utf-8");
1148
+ }
1149
+
962
1150
  // src/index.ts
963
1151
  var program = new Command();
964
1152
  program.name("lunar").description("CLI for Lunar Kit - Universal React Native UI components").version("0.1.0");
965
1153
  program.command("init").description("Initialize Lunar Kit in your project").action(async () => {
966
1154
  await initProject();
967
1155
  });
968
- program.command("add <component>").description("Add a UI component to your project").action(async (component) => {
969
- await addComponent(component);
1156
+ program.command("add <component>").description("Add a UI component or locale entry to your project").action(async (component) => {
1157
+ if (component === "locale") {
1158
+ await addLocaleEntry();
1159
+ } else {
1160
+ await addComponent(component);
1161
+ }
970
1162
  });
971
1163
  var generate = program.command("generate").alias("g").description("Generate modules, screens, components, stores, or hooks");
972
- generate.command("mod <path>").description("Generate a new module (e.g., auth/login)").action(async (path8) => {
973
- await generateModule(path8);
1164
+ generate.command("mod <path>").description("Generate a new module (e.g., auth/login)").action(async (path9) => {
1165
+ await generateModule(path9);
974
1166
  });
975
- generate.command("tabs <path>").description("Generate a tabs module (e.g., shop/main)").action(async (path8) => {
976
- await generateTabs(path8);
1167
+ generate.command("tabs <path>").description("Generate a tabs module (e.g., shop/main)").action(async (path9) => {
1168
+ await generateTabs(path9);
977
1169
  });
978
- generate.command("mod:view <path>").alias("mod:vi").description("Generate view in module (e.g., auth/login-screen)").action(async (path8) => {
979
- await generateModuleView(path8);
1170
+ generate.command("mod:view <path>").alias("mod:vi").description("Generate view in module (e.g., auth/login-screen)").action(async (path9) => {
1171
+ await generateModuleView(path9);
980
1172
  });
981
- generate.command("mod:component <path>").alias("mod:co").description("Generate component in module (e.g., auth/login-button)").action(async (path8) => {
982
- await generateModuleComponent(path8);
1173
+ generate.command("mod:component <path>").alias("mod:co").description("Generate component in module (e.g., auth/login-button)").action(async (path9) => {
1174
+ await generateModuleComponent(path9);
983
1175
  });
984
- generate.command("mod:store <path>").alias("mod:st").description("Generate store in module (e.g., auth/auth-store)").action(async (path8) => {
985
- await generateModuleStore(path8);
1176
+ generate.command("mod:store <path>").alias("mod:st").description("Generate store in module (e.g., auth/auth-store)").action(async (path9) => {
1177
+ await generateModuleStore(path9);
986
1178
  });
987
- generate.command("mod:hook <path>").alias("mod:ho").description("Generate hook in module (e.g., auth/use-auth)").action(async (path8) => {
988
- await generateModuleHook(path8);
1179
+ generate.command("mod:hook <path>").alias("mod:ho").description("Generate hook in module (e.g., auth/use-auth)").action(async (path9) => {
1180
+ await generateModuleHook(path9);
989
1181
  });
990
1182
  generate.command("component <name>").alias("co").description("Generate global component").action(async (name) => {
991
1183
  await generateGlobalComponent(name);
@@ -996,41 +1188,50 @@ generate.command("hook <name>").alias("ho").description("Generate global hook").
996
1188
  generate.command("store <name>").alias("st").description("Generate global store").action(async (name) => {
997
1189
  await generateGlobalStore(name);
998
1190
  });
1191
+ generate.command("locale <lang>").alias("lo").description("Generate a new locale file (e.g., zh, ja, ko)").action(async (lang) => {
1192
+ await generateLocale(lang);
1193
+ });
999
1194
  program.configureHelp({
1000
1195
  formatHelp: () => {
1001
1196
  return `
1002
- ${chalk7.bold("Usage:")} lunar <command> [options]
1197
+ ${chalk8.bold("Usage:")} lunar <command> [options]
1003
1198
 
1004
- ${chalk7.bold("Commands:")}
1005
- ${chalk7.green("init")} Initialize Lunar Kit in your project
1006
- ${chalk7.green("add")} <component> Add a UI component to your project
1007
- ${chalk7.green("generate|g")} [options] <schematic> [path] Generate a Lunar Kit element.
1008
- ${chalk7.dim("Schematics available:")}
1199
+ ${chalk8.bold("Commands:")}
1200
+ ${chalk8.green("init")} Initialize Lunar Kit in your project
1201
+ ${chalk8.green("add")} <component> Add a UI component to your project
1202
+ ${chalk8.green("add")} locale Add a translation entry to all locale files
1203
+ ${chalk8.green("generate|g")} [options] <schematic> [path] Generate a Lunar Kit element.
1204
+ ${chalk8.dim("Schematics available:")}
1009
1205
  \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
1010
- \u2502 ${chalk7.bold("name")} \u2502 ${chalk7.bold("alias")} \u2502 ${chalk7.bold("description")} \u2502
1206
+ \u2502 ${chalk8.bold("name")} \u2502 ${chalk8.bold("alias")} \u2502 ${chalk8.bold("description")} \u2502
1011
1207
  \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524
1012
- \u2502 ${chalk7.cyan("Module")} \u2502 \u2502 \u2502
1208
+ \u2502 ${chalk8.cyan("Module")} \u2502 \u2502 \u2502
1013
1209
  \u2502 mod \u2502 mod \u2502 Generate a module \u2502
1014
1210
  \u2502 tabs \u2502 tabs \u2502 Generate a tabs module \u2502
1015
1211
  \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524
1016
- \u2502 ${chalk7.cyan("Module-Scoped")} \u2502 \u2502 \u2502
1212
+ \u2502 ${chalk8.cyan("Module-Scoped")} \u2502 \u2502 \u2502
1017
1213
  \u2502 mod:view \u2502 mod:vi \u2502 Generate a view in module \u2502
1018
1214
  \u2502 mod:component \u2502 mod:co \u2502 Generate a component in module \u2502
1019
1215
  \u2502 mod:store \u2502 mod:st \u2502 Generate a store in module \u2502
1020
1216
  \u2502 mod:hook \u2502 mod:ho \u2502 Generate a hook in module \u2502
1021
1217
  \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524
1022
- \u2502 ${chalk7.cyan("Global")} \u2502 \u2502 \u2502
1218
+ \u2502 ${chalk8.cyan("Global")} \u2502 \u2502 \u2502
1023
1219
  \u2502 component \u2502 co \u2502 Generate a global component \u2502
1024
1220
  \u2502 hook \u2502 ho \u2502 Generate a global hook \u2502
1025
1221
  \u2502 store \u2502 st \u2502 Generate a global store \u2502
1222
+ \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524
1223
+ \u2502 ${chalk8.cyan("Localization")} \u2502 \u2502 \u2502
1224
+ \u2502 locale \u2502 lo \u2502 Generate a new locale file \u2502
1026
1225
  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
1027
1226
 
1028
- ${chalk7.bold("Examples:")}
1029
- ${chalk7.dim("$ lunar init")}
1030
- ${chalk7.dim("$ lunar add button")}
1031
- ${chalk7.dim("$ lunar g mod auth/login")}
1032
- ${chalk7.dim("$ lunar g mod:vi auth/login-screen")}
1033
- ${chalk7.dim("$ lunar g tabs shop")}
1227
+ ${chalk8.bold("Examples:")}
1228
+ ${chalk8.dim("$ lunar init")}
1229
+ ${chalk8.dim("$ lunar add button")}
1230
+ ${chalk8.dim("$ lunar add locale")}
1231
+ ${chalk8.dim("$ lunar g mod auth/login")}
1232
+ ${chalk8.dim("$ lunar g mod:vi auth/login-screen")}
1233
+ ${chalk8.dim("$ lunar g locale zh")}
1234
+ ${chalk8.dim("$ lunar g tabs shop")}
1034
1235
  `;
1035
1236
  }
1036
1237
  });
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@lunar-kit/cli",
3
- "version": "0.1.3",
3
+ "version": "0.1.7",
4
4
  "description": "CLI for Lunar Kit",
5
5
  "bin": "./dist/index.js",
6
6
  "type": "module",
7
7
  "scripts": {
8
8
  "build": "tsup",
9
9
  "dev": "tsup --watch",
10
- "prepublishOnly": "pnpm build"
10
+ "prepublishOnly": "bun run build"
11
11
  },
12
12
  "dependencies": {
13
13
  "@lunar-kit/core": "0.1.0",
@@ -27,7 +27,7 @@
27
27
  "files": [
28
28
  "dist"
29
29
  ],
30
- "keywords": [
30
+ "keywords": [
31
31
  "react-native",
32
32
  "cli",
33
33
  "lunar-kit",