@linguanahq/cli 0.1.0 → 0.1.1

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 (163) hide show
  1. package/README.md +3 -3
  2. package/dist/index.js +42 -15
  3. package/dist/templates/blank/react/.env.example +15 -0
  4. package/dist/templates/blank/react/ASSETS.md +1 -0
  5. package/dist/templates/blank/react/DEPLOY.md +23 -0
  6. package/dist/templates/blank/react/LICENSE +21 -0
  7. package/dist/templates/blank/react/README.md +34 -0
  8. package/dist/templates/blank/react/_gitignore +11 -0
  9. package/dist/templates/blank/react/index.html +2 -0
  10. package/dist/templates/blank/react/linguana.json +10 -0
  11. package/dist/templates/blank/react/package.json +34 -0
  12. package/dist/templates/blank/react/public/favicon.ico +0 -0
  13. package/dist/templates/blank/react/public/logo.png +0 -0
  14. package/dist/templates/blank/react/src/App.tsx +72 -0
  15. package/dist/templates/blank/react/src/Copy.tsx +36 -0
  16. package/dist/templates/blank/react/src/demo-messages.ts +30 -0
  17. package/dist/templates/blank/react/src/i18n.ts +62 -0
  18. package/dist/templates/blank/react/src/main.tsx +11 -0
  19. package/dist/templates/blank/react/src/messages.ts +10 -0
  20. package/dist/templates/blank/react/src/styles.css +93 -0
  21. package/dist/templates/blank/react/src/vite-env.d.ts +1 -0
  22. package/dist/templates/blank/react/tsconfig.json +23 -0
  23. package/dist/templates/blank/react/vite.config.ts +36 -0
  24. package/dist/templates/blank/react/wrangler.jsonc +8 -0
  25. package/dist/templates/blank/solidjs/.env.example +15 -0
  26. package/dist/templates/blank/solidjs/ASSETS.md +1 -0
  27. package/dist/templates/blank/solidjs/DEPLOY.md +23 -0
  28. package/dist/templates/blank/solidjs/LICENSE +21 -0
  29. package/dist/templates/blank/solidjs/README.md +34 -0
  30. package/dist/templates/blank/solidjs/_gitignore +11 -0
  31. package/dist/templates/blank/solidjs/index.html +2 -0
  32. package/dist/templates/blank/solidjs/linguana.json +10 -0
  33. package/dist/templates/blank/solidjs/package.json +31 -0
  34. package/dist/templates/blank/solidjs/public/favicon.ico +0 -0
  35. package/dist/templates/blank/solidjs/public/logo.png +0 -0
  36. package/dist/templates/blank/solidjs/src/App.tsx +68 -0
  37. package/dist/templates/blank/solidjs/src/Copy.tsx +36 -0
  38. package/dist/templates/blank/solidjs/src/demo-messages.ts +30 -0
  39. package/dist/templates/blank/solidjs/src/i18n.ts +62 -0
  40. package/dist/templates/blank/solidjs/src/main.tsx +14 -0
  41. package/dist/templates/blank/solidjs/src/messages.ts +10 -0
  42. package/dist/templates/blank/solidjs/src/styles.css +93 -0
  43. package/dist/templates/blank/solidjs/src/vite-env.d.ts +1 -0
  44. package/dist/templates/blank/solidjs/tsconfig.json +24 -0
  45. package/dist/templates/blank/solidjs/vite.config.ts +36 -0
  46. package/dist/templates/blank/solidjs/wrangler.jsonc +8 -0
  47. package/dist/templates/blank/svelte/.env.example +15 -0
  48. package/dist/templates/blank/svelte/ASSETS.md +1 -0
  49. package/dist/templates/blank/svelte/DEPLOY.md +23 -0
  50. package/dist/templates/blank/svelte/LICENSE +21 -0
  51. package/dist/templates/blank/svelte/README.md +34 -0
  52. package/dist/templates/blank/svelte/_gitignore +11 -0
  53. package/dist/templates/blank/svelte/index.html +2 -0
  54. package/dist/templates/blank/svelte/linguana.json +10 -0
  55. package/dist/templates/blank/svelte/package.json +32 -0
  56. package/dist/templates/blank/svelte/public/favicon.ico +0 -0
  57. package/dist/templates/blank/svelte/public/logo.png +0 -0
  58. package/dist/templates/blank/svelte/src/App.svelte +40 -0
  59. package/dist/templates/blank/svelte/src/Copy.svelte +14 -0
  60. package/dist/templates/blank/svelte/src/Root.svelte +11 -0
  61. package/dist/templates/blank/svelte/src/demo-messages.ts +30 -0
  62. package/dist/templates/blank/svelte/src/i18n.ts +62 -0
  63. package/dist/templates/blank/svelte/src/main.ts +5 -0
  64. package/dist/templates/blank/svelte/src/messages.ts +10 -0
  65. package/dist/templates/blank/svelte/src/styles.css +93 -0
  66. package/dist/templates/blank/svelte/src/vite-env.d.ts +1 -0
  67. package/dist/templates/blank/svelte/svelte.config.js +2 -0
  68. package/dist/templates/blank/svelte/tsconfig.json +23 -0
  69. package/dist/templates/blank/svelte/vite.config.ts +36 -0
  70. package/dist/templates/blank/svelte/wrangler.jsonc +8 -0
  71. package/dist/templates/blank/tanstack-start/.env.example +15 -0
  72. package/dist/templates/blank/tanstack-start/ASSETS.md +1 -0
  73. package/dist/templates/blank/tanstack-start/DEPLOY.md +23 -0
  74. package/dist/templates/blank/tanstack-start/LICENSE +21 -0
  75. package/dist/templates/blank/tanstack-start/README.md +34 -0
  76. package/dist/templates/blank/tanstack-start/_gitignore +11 -0
  77. package/dist/templates/blank/tanstack-start/linguana.json +10 -0
  78. package/dist/templates/blank/tanstack-start/nitro.config.ts +2 -0
  79. package/dist/templates/blank/tanstack-start/package.json +42 -0
  80. package/dist/templates/blank/tanstack-start/public/favicon.ico +0 -0
  81. package/dist/templates/blank/tanstack-start/public/logo.png +0 -0
  82. package/dist/templates/blank/tanstack-start/src/App.tsx +72 -0
  83. package/dist/templates/blank/tanstack-start/src/Copy.tsx +36 -0
  84. package/dist/templates/blank/tanstack-start/src/demo-messages.ts +30 -0
  85. package/dist/templates/blank/tanstack-start/src/i18n.ts +62 -0
  86. package/dist/templates/blank/tanstack-start/src/locale-server.ts +54 -0
  87. package/dist/templates/blank/tanstack-start/src/messages.ts +10 -0
  88. package/dist/templates/blank/tanstack-start/src/router.tsx +10 -0
  89. package/dist/templates/blank/tanstack-start/src/routes/__root.tsx +53 -0
  90. package/dist/templates/blank/tanstack-start/src/routes/index.tsx +5 -0
  91. package/dist/templates/blank/tanstack-start/src/styles.css +93 -0
  92. package/dist/templates/blank/tanstack-start/src/vite-env.d.ts +1 -0
  93. package/dist/templates/blank/tanstack-start/tsconfig.json +23 -0
  94. package/dist/templates/blank/tanstack-start/vite.config.ts +50 -0
  95. package/dist/templates/blank/tanstack-start/wrangler.jsonc +12 -0
  96. package/dist/templates/blank/vue/.env.example +15 -0
  97. package/dist/templates/blank/vue/ASSETS.md +1 -0
  98. package/dist/templates/blank/vue/DEPLOY.md +23 -0
  99. package/dist/templates/blank/vue/LICENSE +21 -0
  100. package/dist/templates/blank/vue/README.md +34 -0
  101. package/dist/templates/blank/vue/_gitignore +11 -0
  102. package/dist/templates/blank/vue/index.html +2 -0
  103. package/dist/templates/blank/vue/linguana.json +10 -0
  104. package/dist/templates/blank/vue/package.json +34 -0
  105. package/dist/templates/blank/vue/public/favicon.ico +0 -0
  106. package/dist/templates/blank/vue/public/logo.png +0 -0
  107. package/dist/templates/blank/vue/src/App.vue +33 -0
  108. package/dist/templates/blank/vue/src/Copy.vue +13 -0
  109. package/dist/templates/blank/vue/src/demo-messages.ts +30 -0
  110. package/dist/templates/blank/vue/src/i18n.ts +62 -0
  111. package/dist/templates/blank/vue/src/main.ts +7 -0
  112. package/dist/templates/blank/vue/src/messages.ts +10 -0
  113. package/dist/templates/blank/vue/src/styles.css +93 -0
  114. package/dist/templates/blank/vue/src/vite-env.d.ts +1 -0
  115. package/dist/templates/blank/vue/tsconfig.json +23 -0
  116. package/dist/templates/blank/vue/vite.config.ts +36 -0
  117. package/dist/templates/blank/vue/wrangler.jsonc +8 -0
  118. package/dist/templates/blog/react/.env.example +2 -2
  119. package/dist/templates/blog/react/linguana.json +1 -1
  120. package/dist/templates/blog/react/src/App.tsx +1 -1
  121. package/dist/templates/blog/solidjs/.env.example +2 -2
  122. package/dist/templates/blog/solidjs/linguana.json +1 -1
  123. package/dist/templates/blog/solidjs/src/App.tsx +1 -1
  124. package/dist/templates/blog/svelte/.env.example +2 -2
  125. package/dist/templates/blog/svelte/linguana.json +1 -1
  126. package/dist/templates/blog/svelte/src/App.svelte +1 -1
  127. package/dist/templates/blog/tanstack-start/.env.example +2 -2
  128. package/dist/templates/blog/tanstack-start/linguana.json +1 -1
  129. package/dist/templates/blog/tanstack-start/src/App.tsx +1 -1
  130. package/dist/templates/blog/vue/.env.example +2 -2
  131. package/dist/templates/blog/vue/linguana.json +1 -1
  132. package/dist/templates/blog/vue/src/App.vue +1 -1
  133. package/dist/templates/saas/react/.env.example +2 -2
  134. package/dist/templates/saas/react/linguana.json +1 -1
  135. package/dist/templates/saas/react/src/App.tsx +1 -1
  136. package/dist/templates/saas/solidjs/.env.example +2 -2
  137. package/dist/templates/saas/solidjs/linguana.json +1 -1
  138. package/dist/templates/saas/solidjs/src/App.tsx +1 -1
  139. package/dist/templates/saas/svelte/.env.example +2 -2
  140. package/dist/templates/saas/svelte/linguana.json +1 -1
  141. package/dist/templates/saas/svelte/src/App.svelte +1 -1
  142. package/dist/templates/saas/tanstack-start/.env.example +2 -2
  143. package/dist/templates/saas/tanstack-start/linguana.json +1 -1
  144. package/dist/templates/saas/tanstack-start/src/App.tsx +1 -1
  145. package/dist/templates/saas/vue/.env.example +2 -2
  146. package/dist/templates/saas/vue/linguana.json +1 -1
  147. package/dist/templates/saas/vue/src/App.vue +1 -1
  148. package/dist/templates/storefront/react/.env.example +2 -2
  149. package/dist/templates/storefront/react/linguana.json +1 -1
  150. package/dist/templates/storefront/react/src/App.tsx +1 -1
  151. package/dist/templates/storefront/solidjs/.env.example +2 -2
  152. package/dist/templates/storefront/solidjs/linguana.json +1 -1
  153. package/dist/templates/storefront/solidjs/src/App.tsx +1 -1
  154. package/dist/templates/storefront/svelte/.env.example +2 -2
  155. package/dist/templates/storefront/svelte/linguana.json +1 -1
  156. package/dist/templates/storefront/svelte/src/App.svelte +1 -1
  157. package/dist/templates/storefront/tanstack-start/.env.example +2 -2
  158. package/dist/templates/storefront/tanstack-start/linguana.json +1 -1
  159. package/dist/templates/storefront/tanstack-start/src/App.tsx +1 -1
  160. package/dist/templates/storefront/vue/.env.example +2 -2
  161. package/dist/templates/storefront/vue/linguana.json +1 -1
  162. package/dist/templates/storefront/vue/src/App.vue +1 -1
  163. package/package.json +24 -36
package/README.md CHANGED
@@ -8,7 +8,7 @@ cd my-blog
8
8
  npm run dev
9
9
  ```
10
10
 
11
- Choose from `blog` (Elsewhere), `saas` (Relay), and `storefront` (Common Goods) on React, Vue, Svelte, SolidJS, or TanStack Start. All 15 apps are standalone and include local assets, English/French/Spanish/Arabic demo catalogs, deployment configuration, and an MIT license. Demo mode makes no Linguana API requests.
11
+ Choose from `blank` (a minimal translated hello world with hot reload), `blog` (Elsewhere), `saas` (Relay), and `storefront` (Common Goods) on React, Vue, Svelte, SolidJS, or TanStack Start. All 20 apps are standalone and include local assets, English/French/Spanish/Arabic demo catalogs, deployment configuration, and an MIT license. Demo mode makes no Linguana API requests.
12
12
 
13
13
  **Initial release availability:** local demo scaffolding is available now. The hosted API and browser authorization deployment are pending, so `login`, `--connect`, `link`, and `env pull` require a compatible self-hosted server supplied with `--api-url` until that deployment is complete. Hosted template demo links are not included yet; run the generated apps locally using the commands above.
14
14
 
@@ -34,10 +34,10 @@ Owner/Admin access is required to create credentials. Provisioning reuses existi
34
34
 
35
35
  Options include `--package-manager npm|pnpm|yarn|bun`, `--skip-install`, `--organization`, `--project`, `--new-organization`, `--new-project`, `--locales`, `--environment`, `--api-url`, `--yes`, and `--json`. Run each command with `--help` for its supported options.
36
36
 
37
- The default API is `https://linguana-api.mohammedibrahim.dev`. Overrides require HTTPS, except HTTP loopback development. Authenticated requests do not follow redirects. `LINGUANA_CONFIG_DIR` overrides account-session storage; `LINGUANA_AUTH_TOKEN` can supply an account session for controlled automation. Never include either session credentials or build tokens in browser variables, committed files, or shared logs.
37
+ The default API is `https://api.linguana.dev`. Overrides require HTTPS, except HTTP loopback development. Authenticated requests do not follow redirects. `LINGUANA_CONFIG_DIR` overrides account-session storage; `LINGUANA_AUTH_TOKEN` can supply an account session for controlled automation. Never include either session credentials or build tokens in browser variables, committed files, or shared logs.
38
38
 
39
39
  Exit codes: success `0`, operational failure `1`, invalid input `2`, cancellation `130`. If dependency installation fails, the generated app is retained. Interrupted connected setup can be resumed with `link`; its nonsecret recovery journal is `.linguana/setup.json`.
40
40
 
41
41
  Every generated app includes `DEPLOY.md` for Cloudflare and generic static hosting (or a Node server for TanStack Start). Hosting authentication and deployment are outside this CLI.
42
42
 
43
- [Full documentation](https://linguana-docs.mohammedibrahim.dev/docs/cli). New CLI and template code: MIT.
43
+ [Full documentation](https://docs.linguana.dev/docs/cli). New CLI and template code: MIT.
package/dist/index.js CHANGED
@@ -6,14 +6,17 @@ import { join as join5, resolve as resolve4 } from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import { group } from "@clack/prompts";
8
8
 
9
+ // package.json
10
+ var version = "0.1.1";
11
+
9
12
  // ../templates/src/deployments.json
10
13
  var deployments_default = {};
11
14
 
12
15
  // ../templates/src/registry.ts
13
16
  var templateVersion = "0.1.0";
14
- var templateSourcePackageVersion = "0.1.0";
17
+ var templateSourcePackageVersion = version;
15
18
  var sdkVersion = "0.1.1";
16
- var defaultApiUrl = "https://linguana-api.mohammedibrahim.dev";
19
+ var defaultApiUrl = "https://api.linguana.dev";
17
20
  var frameworks = [
18
21
  "react",
19
22
  "vue",
@@ -28,7 +31,19 @@ var frameworkLabels = {
28
31
  solidjs: "SolidJS",
29
32
  "tanstack-start": "TanStack Start"
30
33
  };
31
- var templateIds = ["blog", "saas", "storefront"];
34
+ var templateIds = ["blank", "blog", "saas", "storefront"];
35
+ var blankTemplate = {
36
+ id: "blank",
37
+ name: "Blank",
38
+ category: "Hello world",
39
+ description: "A minimal starting point: the Linguana logo, a translated hello world, a language picker, and hot reload.",
40
+ color: "#2f5bea",
41
+ features: [
42
+ "Translated hello world",
43
+ "Language picker with RTL",
44
+ "Hot module reload"
45
+ ]
46
+ };
32
47
  var templates = [
33
48
  {
34
49
  id: "blog",
@@ -67,8 +82,9 @@ var templates = [
67
82
  ]
68
83
  }
69
84
  ];
85
+ var allTemplates = [blankTemplate, ...templates];
70
86
  var deployments = deployments_default;
71
- var variants = templates.flatMap(
87
+ var variants = allTemplates.flatMap(
72
88
  (template) => frameworks.map((framework) => {
73
89
  const id = `${template.id}/${framework}`;
74
90
  return {
@@ -83,7 +99,7 @@ var variants = templates.flatMap(
83
99
  locales: ["en", "fr", "es", "ar"],
84
100
  sourceUrl: `https://unpkg.com/@linguanahq/cli@${templateSourcePackageVersion}/dist/templates/${id}/`,
85
101
  demoUrl: deployments[id]?.version === templateVersion ? deployments[id]?.url : void 0,
86
- screenshot: `/templates/${template.id}-${framework}.png`,
102
+ screenshot: template.id === "blank" ? void 0 : `/templates/${template.id}-${framework}.png`,
87
103
  deploymentTargets: framework === "tanstack-start" ? ["cloudflare", "node"] : ["cloudflare", "static"]
88
104
  };
89
105
  })
@@ -186,6 +202,7 @@ function safeMessage(error) {
186
202
  }
187
203
 
188
204
  // src/api.ts
205
+ var retiredOrigins = /* @__PURE__ */ new Set(["https://linguana-api.mohammedibrahim.dev"]);
189
206
  function apiOrigin(value = defaultApiUrl) {
190
207
  let url;
191
208
  try {
@@ -200,7 +217,7 @@ function apiOrigin(value = defaultApiUrl) {
200
217
  "INVALID_API_URL",
201
218
  2
202
219
  );
203
- return url.origin;
220
+ return retiredOrigins.has(url.origin) ? new URL(defaultApiUrl).origin : url.origin;
204
221
  }
205
222
  var ApiError = class extends CliError {
206
223
  constructor(status, code, message, requestId) {
@@ -233,11 +250,18 @@ var Api = class {
233
250
  ]),
234
251
  headers: {
235
252
  "content-type": "application/json",
236
- "user-agent": "linguana-cli/0.1.0",
253
+ "user-agent": `linguana-cli/${version}`,
237
254
  ...this.token ? { authorization: `Bearer ${this.token}` } : {},
238
255
  ...options.key ? { "idempotency-key": options.key } : {}
239
256
  },
240
257
  body: options.body === void 0 ? void 0 : JSON.stringify(options.body)
258
+ }).catch((error) => {
259
+ if (cancellation.signal.aborted || options.signal?.aborted) throw error;
260
+ const cause = error instanceof Error && error.cause instanceof Error ? error.cause.code ?? error.cause.message : error instanceof Error ? error.name === "TimeoutError" ? "timed out" : error.message : "network error";
261
+ throw new CliError(
262
+ `Could not reach Linguana at ${this.origin} (${cause}). Check your connection, or pass --api-url.`,
263
+ "NETWORK_ERROR"
264
+ );
241
265
  });
242
266
  const body = await response.json().catch(() => ({}));
243
267
  if (!response.ok)
@@ -1187,7 +1211,7 @@ process.once("SIGINT", () => {
1187
1211
  cancellation.abort();
1188
1212
  process.exitCode = 130;
1189
1213
  });
1190
- var program = new Command().name("linguana").description("Create and connect multilingual apps with Linguana.").version("0.1.0").option("--json", "Print structured results without secrets").option("--yes", "Use defaults without prompting").option("--api-url <origin>", "Linguana API origin").showHelpAfterError().exitOverride();
1214
+ var program = new Command().name("linguana").description("Create and connect multilingual apps with Linguana.").version(version).option("--json", "Print structured results without secrets").option("--yes", "Use defaults without prompting").option("--api-url <origin>", "Linguana API origin").showHelpAfterError().exitOverride();
1191
1215
  var opts = (command) => command.optsWithGlobals();
1192
1216
  function projectOptions(command) {
1193
1217
  return command.option("--organization <id>", "Existing organization ID").option("--project <id>", "Existing project ID").option("--new-organization <name>", "Create an organization").option("--new-project <name>", "Create a project").option(
@@ -1275,7 +1299,7 @@ async function originFor(options) {
1275
1299
  );
1276
1300
  }
1277
1301
  projectOptions(
1278
- program.command("create").description("Create a new app").command("app [directory]").option("--template <template>", "blog, saas, or storefront").option("--demo", "Use bundled translations without an account").option("--connect", "Connect a Linguana project").option("--package-manager <manager>", "npm, pnpm, yarn, or bun").option("--skip-install", "Write files without installing dependencies")
1302
+ program.command("create").description("Create a new app").command("app [directory]").option("--template <template>", "blank, blog, saas, or storefront").option("--demo", "Use bundled translations without an account").option("--connect", "Connect a Linguana project").option("--package-manager <manager>", "npm, pnpm, yarn, or bun").option("--skip-install", "Write files without installing dependencies")
1279
1303
  ).action(
1280
1304
  async (directory, _local, command) => {
1281
1305
  const options = opts(command);
@@ -1284,7 +1308,7 @@ projectOptions(
1284
1308
  const choices = {
1285
1309
  template: async () => options.template ?? await ui.choose(
1286
1310
  "Choose a template",
1287
- templates.map((item) => ({
1311
+ allTemplates.map((item) => ({
1288
1312
  value: item.id,
1289
1313
  label: `${item.name} \u2014 ${item.category}`
1290
1314
  }))
@@ -1301,7 +1325,7 @@ projectOptions(
1301
1325
  const template = selectedChoices.template;
1302
1326
  if (!templateIds.includes(template))
1303
1327
  throw new CliError(
1304
- "Template must be blog, saas, or storefront.",
1328
+ "Template must be blank, blog, saas, or storefront.",
1305
1329
  "INVALID_TEMPLATE",
1306
1330
  2
1307
1331
  );
@@ -1309,7 +1333,10 @@ projectOptions(
1309
1333
  const framework = normalizeFramework(selected);
1310
1334
  if (!framework)
1311
1335
  throw new CliError("Unsupported framework.", "INVALID_FRAMEWORK", 2);
1312
- const target = directory ?? await ui.input("Where should we create your app?", `my-${template}`);
1336
+ const target = directory ?? await ui.input(
1337
+ "Where should we create your app?",
1338
+ template === "blank" ? "my-app" : `my-${template}`
1339
+ );
1313
1340
  try {
1314
1341
  await validateDestination(target);
1315
1342
  } catch (error) {
@@ -1399,8 +1426,8 @@ Builds upload manifests. Paid translation and production publishing remain expli
1399
1426
  program.command("templates").command("list").action((_local, command) => {
1400
1427
  const ui = new Ui(opts(command));
1401
1428
  ui.result(
1402
- { templates, variants },
1403
- templates.map(
1429
+ { templates: allTemplates, variants },
1430
+ allTemplates.map(
1404
1431
  (item) => `${item.id.padEnd(12)} ${item.name} \u2014 ${item.description}
1405
1432
  ${frameworks.join(", ")}${variants.filter(
1406
1433
  (variant) => variant.template === item.id && variant.demoUrl
@@ -1485,7 +1512,7 @@ projectOptions(
1485
1512
  result,
1486
1513
  `Connected ${result.projectId}. Wrote ${result.envFile}.
1487
1514
  Project: ${result.projectUrl}
1488
- ${frameworkLabels[result.config.framework]} integration: https://linguana-docs.mohammedibrahim.dev/docs/${integration}
1515
+ ${frameworkLabels[result.config.framework]} integration: https://docs.linguana.dev/docs/${integration}
1489
1516
  Add the Linguana Vite adapter before your framework plugin and load the environment with loadEnv. ${provider} See the integration guide for exact imports.`
1490
1517
  );
1491
1518
  });
@@ -0,0 +1,15 @@
1
+ # Demo mode works without credentials. Connect with npx @linguanahq/cli link.
2
+ # Copying this file alone does not enable connected mode; use linguana.json.
3
+ # Build/server only. NEVER prefix the project token with VITE_.
4
+ LINGUANA_PROJECT_ID=
5
+ LINGUANA_ENVIRONMENT=development
6
+ LINGUANA_API_URL=https://api.linguana.dev
7
+ LINGUANA_PROJECT_TOKEN=
8
+ LINGUANA_UPLOAD=false
9
+ # Paid translation is disabled; this template does not enable it automatically.
10
+ LINGUANA_TRANSLATE=false
11
+ # Browser-safe values. A publishable key reads published catalogs only.
12
+ VITE_LINGUANA_PROJECT_ID=
13
+ VITE_LINGUANA_ENVIRONMENT=development
14
+ VITE_LINGUANA_API_URL=https://api.linguana.dev
15
+ VITE_LINGUANA_PUBLISHABLE_KEY=
@@ -0,0 +1 @@
1
+ The Linguana logo and favicon in public/ are Linguana brand assets, included so the starter renders out of the box. Replace them with your own. Sample translations are MIT. No remote images, tracking pixels, or font services are used.
@@ -0,0 +1,23 @@
1
+ # Deploy Blank
2
+
3
+ Demo mode needs no Linguana account. Hosting authentication is separate from the Linguana CLI.
4
+
5
+ ## Cloudflare Workers
6
+
7
+ Install dependencies, choose a unique Worker name in wrangler.jsonc, then:
8
+
9
+ ```sh
10
+ npx wrangler login
11
+ npm run build
12
+ npx wrangler deploy
13
+ ```
14
+
15
+ Wrangler serves dist as static assets with single-page navigation fallback. Unknown paths fall back to index.html.
16
+
17
+ ## Any static host
18
+
19
+ Run `npm run build` and publish dist/. Configure unknown paths to serve /index.html with status 200 (SPA fallback). A static preview can be started with `npm run preview`. Do not publish the project root.
20
+
21
+ ## Connected deployments
22
+
23
+ Pull the intended environment with `linguana env pull --environment production`. Configure the variables from .env.example in the build host, including the matching public project/environment values. Build tokens are secrets used by the compiler only. Never upload .env files or the CLI account session. LINGUANA_TRANSLATE remains false. Catalog publication is an explicit dashboard action.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Linguana contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,34 @@
1
+ # Blank
2
+
3
+ A minimal starting point: the Linguana logo, a translated hello world, a language picker, and hot reload.
4
+
5
+ A standalone react app, generated from Linguana templates v0.1.0. Requires Node 22.12+ (or Bun).
6
+
7
+ ## Run
8
+
9
+ ```sh
10
+ npm install
11
+ npm run dev
12
+ ```
13
+
14
+ You can also use pnpm, Yarn, or Bun. English, French, Spanish, and Arabic catalogs are bundled in demo mode. No Linguana API requests are made. The language cookie persists between visits. Arabic switches the document to RTL.
15
+
16
+ ## Connect your own project
17
+
18
+ ```sh
19
+ npx @linguanahq/cli login
20
+ npx @linguanahq/cli link
21
+ npx @linguanahq/cli doctor
22
+ ```
23
+
24
+ Linking writes nonsecret `linguana.json` and ignored `.env.local`. The account session stays outside this project. Only the public project ID, environment, API URL, and publishable key reach the browser. Build tokens never belong in `VITE_*` variables.
25
+
26
+ Connected mode uses your published project catalogs and English source fallback. It does not load demo translations. Builds can upload the source manifest but do not start paid translations or publish catalogs. Review and publish translations in your dashboard. Existing project languages remain unchanged unless you pass `--locales`.
27
+
28
+ To target another environment, use `linguana env pull --environment staging`, then `npm run build -- --mode staging`. Production uses `.env.production.local` and Vite's default production build mode. Configure matching private and public values on your build host; see .env.example.
29
+
30
+ ## Customize
31
+
32
+ Start in src/App.tsx. Vite hot reload applies your edits as you save, without losing the selected language. Add a message by giving it a stable ID in src/Copy.tsx with its English text, a source fallback in src/messages.ts, and demo translations in src/demo-messages.ts.
33
+
34
+ Run `npm run check-types` and `npm run build` before deploying. Deployment instructions are in DEPLOY.md. See LICENSE and ASSETS.md.
@@ -0,0 +1,11 @@
1
+ node_modules/
2
+ dist/
3
+ .output/
4
+ .nitro/
5
+ .tanstack/
6
+ .wrangler/
7
+ .env
8
+ .env.local
9
+ .env.*.local
10
+ .linguana/
11
+ *.log
@@ -0,0 +1,2 @@
1
+ <!doctype html>
2
+ <html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta name="description" content="A minimal starting point: the Linguana logo, a translated hello world, a language picker, and hot reload."><meta name="theme-color" content="#2f5bea"><link rel="icon" href="/favicon.ico" sizes="any"><title>Blank</title></head><body><div id="app"></div><script type="module" src="/src/main.tsx"></script></body></html>
@@ -0,0 +1,10 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "framework": "react",
4
+ "template": "blank",
5
+ "templateVersion": "0.1.0",
6
+ "mode": "demo",
7
+ "apiUrl": "https://api.linguana.dev",
8
+ "sourceLocale": "en",
9
+ "defaultEnvironment": "development"
10
+ }
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "blank-react",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "engines": {
8
+ "node": ">=22.12.0"
9
+ },
10
+ "scripts": {
11
+ "dev": "vite --host 127.0.0.1",
12
+ "build": "vite build",
13
+ "preview": "vite preview",
14
+ "check-types": "tsc --noEmit",
15
+ "deploy": "npm run build && wrangler deploy"
16
+ },
17
+ "dependencies": {
18
+ "@linguanahq/catalog": "0.1.1",
19
+ "@linguanahq/core": "0.1.1",
20
+ "@linguanahq/react": "0.1.1",
21
+ "react": "^19.2.7",
22
+ "react-dom": "^19.2.7"
23
+ },
24
+ "devDependencies": {
25
+ "@linguanahq/vite": "0.1.1",
26
+ "typescript": "^6.0.0",
27
+ "vite": "^8.1.3",
28
+ "@types/node": "^24.0.0",
29
+ "wrangler": "^4.127.0",
30
+ "@types/react": "^19.2.17",
31
+ "@types/react-dom": "^19.2.3",
32
+ "@vitejs/plugin-react": "^6.0.3"
33
+ }
34
+ }
@@ -0,0 +1,72 @@
1
+ import { useTranslation } from "@linguanahq/react";
2
+ import { Fragment, useEffect } from "react";
3
+ import Copy from "./Copy.tsx";
4
+ import { demoLocales } from "./i18n";
5
+ import { messages } from "./messages";
6
+
7
+ export default function App() {
8
+ const runtime = useTranslation();
9
+ const locale = () => runtime.locale;
10
+ const locales = () =>
11
+ runtime.availableLocales.length
12
+ ? runtime.availableLocales
13
+ : demoLocales.filter((item) => item.code === runtime.sourceLocale);
14
+ const translate = runtime.translate;
15
+ const changeLocale = (value: string) => void runtime.setLocale(value);
16
+ useEffect(() => {
17
+ document.documentElement.lang = runtime.locale;
18
+ document.documentElement.dir = runtime.locale.startsWith("ar")
19
+ ? "rtl"
20
+ : "ltr";
21
+ }, [runtime.locale]);
22
+ const msg = (id: string, values?: Record<string, string | number>) =>
23
+ String(translate(id, messages[id] ?? id, values));
24
+ const value = (event: Event | { currentTarget: EventTarget | null }) =>
25
+ (event.currentTarget as HTMLSelectElement).value;
26
+
27
+ return (
28
+ <main className="hello">
29
+ <img
30
+ src="/logo.png"
31
+ alt={msg("logoAlt")}
32
+ className="hello-logo"
33
+ width={96}
34
+ height={96}
35
+ />
36
+ <h1>
37
+ <Copy name="hello" />
38
+ </h1>
39
+ <p className="hello-intro">
40
+ <Copy name="intro" />
41
+ </p>
42
+ <label className="locale-picker">
43
+ <span className="sr-only">
44
+ <Copy name="language" />
45
+ </span>
46
+ <select
47
+ value={locale()}
48
+ aria-label={msg("language")}
49
+ onChange={(event) => {
50
+ changeLocale(value(event));
51
+ }}
52
+ >
53
+ {locales().map((language) => (
54
+ <Fragment key={language.code}>
55
+ <option value={language.code}>{language.nativeName}</option>
56
+ </Fragment>
57
+ ))}
58
+ </select>
59
+ </label>
60
+ <p className="hello-hint">
61
+ <Copy name="hotReload" values={{ file: "src/App.tsx" }} />
62
+ </p>
63
+ <a
64
+ href="https://docs.linguana.dev"
65
+ className="hello-link"
66
+ rel="noreferrer"
67
+ >
68
+ <Copy name="docs" />
69
+ </a>
70
+ </main>
71
+ );
72
+ }
@@ -0,0 +1,36 @@
1
+ import { T } from "@linguanahq/react";
2
+ export default function Copy(props: {
3
+ name: string;
4
+ values?: Record<string, string | number>;
5
+ }) {
6
+ return (
7
+ <>
8
+ {props.name === "language" && (
9
+ <T id="language" defaultMessage="Language" values={props.values} />
10
+ )}
11
+ {props.name === "hello" && (
12
+ <T id="hello" defaultMessage="Hello, world!" values={props.values} />
13
+ )}
14
+ {props.name === "intro" && (
15
+ <T
16
+ id="intro"
17
+ defaultMessage="Your multilingual app is ready. Pick a language to see it translated."
18
+ values={props.values}
19
+ />
20
+ )}
21
+ {props.name === "hotReload" && (
22
+ <T
23
+ id="hotReload"
24
+ defaultMessage="Edit {file} and save. The page updates instantly."
25
+ values={props.values}
26
+ />
27
+ )}
28
+ {props.name === "docs" && (
29
+ <T id="docs" defaultMessage="Read the docs" values={props.values} />
30
+ )}
31
+ {props.name === "logoAlt" && (
32
+ <T id="logoAlt" defaultMessage="Linguana logo" values={props.values} />
33
+ )}
34
+ </>
35
+ );
36
+ }
@@ -0,0 +1,30 @@
1
+ // These catalogs are only used when linguana.json selects demo mode.
2
+ export const demoMessages = {
3
+ fr: {
4
+ language: "Langue",
5
+ hello: "Bonjour, le monde !",
6
+ intro:
7
+ "Votre application multilingue est prête. Choisissez une langue pour la voir traduite.",
8
+ hotReload:
9
+ "Modifiez {file} et enregistrez. La page se met à jour instantanément.",
10
+ docs: "Lire la documentation",
11
+ logoAlt: "Logo Linguana",
12
+ },
13
+ es: {
14
+ language: "Idioma",
15
+ hello: "¡Hola, mundo!",
16
+ intro:
17
+ "Tu aplicación multilingüe está lista. Elige un idioma para verla traducida.",
18
+ hotReload: "Edita {file} y guarda. La página se actualiza al instante.",
19
+ docs: "Leer la documentación",
20
+ logoAlt: "Logotipo de Linguana",
21
+ },
22
+ ar: {
23
+ language: "اللغة",
24
+ hello: "مرحبًا بالعالم!",
25
+ intro: "تطبيقك متعدد اللغات جاهز. اختر لغة لتراه مترجمًا.",
26
+ hotReload: "عدّل {file} واحفظه. تتحدّث الصفحة فورًا.",
27
+ docs: "اقرأ التوثيق",
28
+ logoAlt: "شعار Linguana",
29
+ },
30
+ };
@@ -0,0 +1,62 @@
1
+ import { createCatalog } from "@linguanahq/catalog";
2
+ import config from "../linguana.json";
3
+ import { demoMessages } from "./demo-messages";
4
+ export const demoLocales = [
5
+ { code: "en", displayName: "English", nativeName: "English" },
6
+ { code: "fr", displayName: "French", nativeName: "Français" },
7
+ { code: "es", displayName: "Spanish", nativeName: "Español" },
8
+ { code: "ar", displayName: "Arabic", nativeName: "العربية" },
9
+ ];
10
+ export const demo = config.mode === "demo";
11
+ export function cookieLocale(
12
+ cookie = typeof document === "undefined" ? "" : document.cookie,
13
+ ) {
14
+ try {
15
+ const value = decodeURIComponent(
16
+ cookie
17
+ .split(";")
18
+ .find((item) => item.trim().startsWith("linguana_locale="))
19
+ ?.split("=")[1] ?? "en",
20
+ );
21
+ return /^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$/.test(value) &&
22
+ (!demo || demoLocales.some((item) => item.code === value))
23
+ ? value
24
+ : "en";
25
+ } catch {
26
+ return "en";
27
+ }
28
+ }
29
+ export const demoCatalogs = demo
30
+ ? Object.entries(demoMessages).map(([locale, messages]) =>
31
+ createCatalog({
32
+ schemaVersion: "1.0",
33
+ projectId: "demo-blank",
34
+ environment: "development",
35
+ sourceLocale: "en",
36
+ locale,
37
+ manifestHash: "0".repeat(64),
38
+ version: 1,
39
+ messages,
40
+ }),
41
+ )
42
+ : [];
43
+ export function optionsFor(
44
+ locale = cookieLocale(),
45
+ ): import("@linguanahq/core").LinguanaClientOptions {
46
+ return {
47
+ projectId: demo ? "demo-blank" : import.meta.env.VITE_LINGUANA_PROJECT_ID,
48
+ environment: demo
49
+ ? "development"
50
+ : import.meta.env.VITE_LINGUANA_ENVIRONMENT,
51
+ initialLocale: locale,
52
+ fallbackLocale: config.sourceLocale,
53
+ catalogs: demoCatalogs,
54
+ ...(demo
55
+ ? { availableLocales: demoLocales }
56
+ : {
57
+ publishableKey: import.meta.env.VITE_LINGUANA_PUBLISHABLE_KEY,
58
+ catalogEndpoint: import.meta.env.VITE_LINGUANA_API_URL,
59
+ configEndpoint: import.meta.env.VITE_LINGUANA_API_URL,
60
+ }),
61
+ };
62
+ }
@@ -0,0 +1,11 @@
1
+ import { LocaleProvider } from "@linguanahq/react";
2
+ import { createRoot } from "react-dom/client";
3
+ import App from "./App";
4
+ import { optionsFor } from "./i18n";
5
+ import "./styles.css";
6
+
7
+ createRoot(document.getElementById("app")!).render(
8
+ <LocaleProvider {...optionsFor()}>
9
+ <App />
10
+ </LocaleProvider>,
11
+ );
@@ -0,0 +1,10 @@
1
+ // Source fallbacks; matching extractable literals live in Copy.tsx.
2
+ export const messages: Record<string, string> = {
3
+ language: "Language",
4
+ hello: "Hello, world!",
5
+ intro:
6
+ "Your multilingual app is ready. Pick a language to see it translated.",
7
+ hotReload: "Edit {file} and save. The page updates instantly.",
8
+ docs: "Read the docs",
9
+ logoAlt: "Linguana logo",
10
+ };