@authrim/setup 0.1.81 → 0.1.84

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 (62) hide show
  1. package/dist/__tests__/migrate.test.js +31 -4
  2. package/dist/__tests__/migrate.test.js.map +1 -1
  3. package/dist/__tests__/paths.test.js.map +1 -1
  4. package/dist/cli/commands/delete.d.ts.map +1 -1
  5. package/dist/cli/commands/delete.js +4 -3
  6. package/dist/cli/commands/delete.js.map +1 -1
  7. package/dist/cli/commands/deploy.d.ts.map +1 -1
  8. package/dist/cli/commands/deploy.js +12 -8
  9. package/dist/cli/commands/deploy.js.map +1 -1
  10. package/dist/cli/commands/info.d.ts.map +1 -1
  11. package/dist/cli/commands/info.js +2 -1
  12. package/dist/cli/commands/info.js.map +1 -1
  13. package/dist/cli/commands/init.d.ts +1 -0
  14. package/dist/cli/commands/init.d.ts.map +1 -1
  15. package/dist/cli/commands/init.js +255 -201
  16. package/dist/cli/commands/init.js.map +1 -1
  17. package/dist/cli/commands/migrate.d.ts.map +1 -1
  18. package/dist/cli/commands/migrate.js +5 -3
  19. package/dist/cli/commands/migrate.js.map +1 -1
  20. package/dist/core/keys.d.ts.map +1 -1
  21. package/dist/core/keys.js.map +1 -1
  22. package/dist/core/migrate.d.ts.map +1 -1
  23. package/dist/core/migrate.js.map +1 -1
  24. package/dist/core/paths.d.ts.map +1 -1
  25. package/dist/core/paths.js +1 -3
  26. package/dist/core/paths.js.map +1 -1
  27. package/dist/core/wrangler-sync.d.ts.map +1 -1
  28. package/dist/core/wrangler-sync.js +2 -2
  29. package/dist/core/wrangler-sync.js.map +1 -1
  30. package/dist/i18n/detector.d.ts +28 -0
  31. package/dist/i18n/detector.d.ts.map +1 -0
  32. package/dist/i18n/detector.js +103 -0
  33. package/dist/i18n/detector.js.map +1 -0
  34. package/dist/i18n/index.d.ts +64 -0
  35. package/dist/i18n/index.d.ts.map +1 -0
  36. package/dist/i18n/index.js +194 -0
  37. package/dist/i18n/index.js.map +1 -0
  38. package/dist/i18n/locales/en.d.ts +8 -0
  39. package/dist/i18n/locales/en.d.ts.map +1 -0
  40. package/dist/i18n/locales/en.js +593 -0
  41. package/dist/i18n/locales/en.js.map +1 -0
  42. package/dist/i18n/locales/ja.d.ts +8 -0
  43. package/dist/i18n/locales/ja.d.ts.map +1 -0
  44. package/dist/i18n/locales/ja.js +593 -0
  45. package/dist/i18n/locales/ja.js.map +1 -0
  46. package/dist/i18n/types.d.ts +303 -0
  47. package/dist/i18n/types.d.ts.map +1 -0
  48. package/dist/i18n/types.js +19 -0
  49. package/dist/i18n/types.js.map +1 -0
  50. package/dist/index.js +1 -0
  51. package/dist/index.js.map +1 -1
  52. package/dist/web/api.d.ts.map +1 -1
  53. package/dist/web/api.js +7 -2
  54. package/dist/web/api.js.map +1 -1
  55. package/dist/web/server.d.ts.map +1 -1
  56. package/dist/web/server.js +21 -2
  57. package/dist/web/server.js.map +1 -1
  58. package/dist/web/ui.d.ts +2 -1
  59. package/dist/web/ui.d.ts.map +1 -1
  60. package/dist/web/ui.js +87 -2
  61. package/dist/web/ui.js.map +1 -1
  62. package/package.json +1 -1
@@ -6,6 +6,7 @@
6
6
  import { input, select, confirm, password } from '@inquirer/prompts';
7
7
  import chalk from 'chalk';
8
8
  import ora from 'ora';
9
+ import { initI18n, t, getAvailableLocales, detectSystemLocale, } from '../../i18n/index.js';
9
10
  import { readFile, writeFile, mkdir } from 'node:fs/promises';
10
11
  import { existsSync } from 'node:fs';
11
12
  import { join, resolve } from 'node:path';
@@ -15,9 +16,27 @@ import { createDefaultConfig, parseConfig } from '../../core/config.js';
15
16
  import { generateAllSecrets, saveKeysToDirectory, generateKeyId, keysExistForEnvironment, } from '../../core/keys.js';
16
17
  import { isWranglerInstalled, checkAuth, provisionResources, toResourceIds, getAccountId, detectEnvironments, getWorkersSubdomain, } from '../../core/cloudflare.js';
17
18
  import { createLockFile, saveLockFile, loadLockFile } from '../../core/lock.js';
18
- import { getEnvironmentPaths, getRelativeKeysPath, AUTHRIM_DIR, } from '../../core/paths.js';
19
+ import { getEnvironmentPaths, getRelativeKeysPath, AUTHRIM_DIR } from '../../core/paths.js';
19
20
  import { downloadSource, verifySourceStructure, checkForUpdate } from '../../core/source.js';
20
21
  // =============================================================================
22
+ // Language Selection
23
+ // =============================================================================
24
+ /**
25
+ * Show language selection prompt
26
+ * This is shown before the banner, so we use hardcoded multilingual prompt
27
+ */
28
+ async function selectLanguage() {
29
+ const locales = getAvailableLocales();
30
+ const locale = await select({
31
+ message: 'Select language / 言語を選択 / 选择语言',
32
+ choices: locales.map((l) => ({
33
+ value: l.code,
34
+ name: `${l.flag || ''} ${l.nativeName}`.trim(),
35
+ })),
36
+ });
37
+ return locale;
38
+ }
39
+ // =============================================================================
21
40
  // Version
22
41
  // =============================================================================
23
42
  const require = createRequire(import.meta.url);
@@ -37,21 +56,23 @@ function getVersion() {
37
56
  function printBanner() {
38
57
  const version = getVersion();
39
58
  const versionStr = `v${version}`.padEnd(7); // "v0.1.58" = 7 chars
59
+ const title = `🔐 ${t('banner.title')} ${versionStr}`;
60
+ const subtitle = t('banner.subtitle');
40
61
  console.log('');
41
62
  console.log(chalk.blue('╔═══════════════════════════════════════════════════════════╗'));
42
63
  console.log(chalk.blue('║') +
43
- chalk.bold.white(` 🔐 Authrim Setup ${versionStr} `) +
64
+ chalk.bold.white(` ${title} `.slice(0, 59)) +
44
65
  chalk.blue('║'));
45
66
  console.log(chalk.blue('║') +
46
- chalk.gray(' OIDC Provider on Cloudflare Workers ') +
67
+ chalk.gray(` ${subtitle} `.slice(0, 55)) +
47
68
  chalk.blue('║'));
48
69
  console.log(chalk.blue('╚═══════════════════════════════════════════════════════════╝'));
49
70
  console.log('');
50
- console.log(chalk.bgYellow.black(' ⚠️ WARNING: Under Development! '));
51
- console.log(chalk.yellow(' This project does not work correctly yet.'));
52
- console.log(chalk.yellow(' Admin UI is incomplete and does not support login.'));
71
+ console.log(chalk.bgYellow.black(` ⚠️ ${t('banner.warning')} `));
72
+ console.log(chalk.yellow(` ${t('banner.warningDetail')}`));
73
+ console.log(chalk.yellow(` ${t('banner.adminWarning')}`));
53
74
  console.log('');
54
- console.log(chalk.gray(' Press Ctrl+C at any time to exit'));
75
+ console.log(chalk.gray(` ${t('banner.exitHint')}`));
55
76
  console.log('');
56
77
  }
57
78
  // Store the workers.dev subdomain for URL generation
@@ -372,6 +393,28 @@ async function updateExistingSource(sourceDir, gitRef) {
372
393
  // Main Command
373
394
  // =============================================================================
374
395
  export async function initCommand(options) {
396
+ // Step 0: Language selection (before banner)
397
+ // Priority: --lang option > env var > system locale > interactive selection
398
+ let locale;
399
+ if (options.lang) {
400
+ // Use provided language option
401
+ locale = options.lang;
402
+ }
403
+ else {
404
+ // Check system locale first
405
+ const systemLocale = detectSystemLocale();
406
+ if (systemLocale !== 'en') {
407
+ // Non-English system locale detected, use it
408
+ locale = systemLocale;
409
+ }
410
+ else {
411
+ // Show language selection prompt
412
+ locale = await selectLanguage();
413
+ }
414
+ }
415
+ // Initialize i18n with selected locale
416
+ await initI18n(locale);
417
+ // Now show the banner in the selected language
375
418
  printBanner();
376
419
  // Load existing config if provided
377
420
  if (options.config) {
@@ -388,35 +431,35 @@ export async function initCommand(options) {
388
431
  // Show startup menu
389
432
  console.log(chalk.gray('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
390
433
  console.log('');
391
- console.log(' Set up Authrim OIDC Provider on Cloudflare Workers.');
434
+ console.log(` ${t('startup.description')}`);
392
435
  console.log('');
393
436
  console.log(chalk.gray('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
394
437
  console.log('');
395
438
  const startupChoice = await select({
396
- message: 'Choose setup method',
439
+ message: t('mode.prompt'),
397
440
  choices: [
398
441
  {
399
442
  value: 'webui',
400
- name: '🌐 Web UI (Recommended)',
401
- description: 'Interactive setup in your browser',
443
+ name: `🌐 ${t('mode.quick')}`,
444
+ description: t('mode.quickDesc'),
402
445
  },
403
446
  {
404
447
  value: 'cli',
405
- name: '⌨️ CLI Mode',
406
- description: 'Interactive setup in terminal',
448
+ name: `⌨️ ${t('mode.advanced')}`,
449
+ description: t('mode.advancedDesc'),
407
450
  },
408
451
  {
409
452
  value: 'cancel',
410
- name: '❌ Cancel',
411
- description: 'Exit setup',
453
+ name: `❌ ${t('startup.cancel')}`,
454
+ description: t('startup.cancelDesc'),
412
455
  },
413
456
  ],
414
457
  });
415
458
  if (startupChoice === 'cancel') {
416
459
  console.log('');
417
- console.log(chalk.gray('Setup cancelled.'));
460
+ console.log(chalk.gray(t('startup.cancelled')));
418
461
  console.log('');
419
- console.log(chalk.gray('To resume later:'));
462
+ console.log(chalk.gray(t('startup.resumeLater')));
420
463
  console.log(chalk.cyan(' npx @authrim/setup'));
421
464
  console.log('');
422
465
  return;
@@ -430,7 +473,7 @@ export async function initCommand(options) {
430
473
  else {
431
474
  // Start Web UI
432
475
  console.log('');
433
- console.log(chalk.cyan('🌐 Starting Web UI...'));
476
+ console.log(chalk.cyan(`🌐 ${t('webUi.starting')}`));
434
477
  console.log('');
435
478
  const { startWebServer } = await import('../../web/server.js');
436
479
  await startWebServer({ openBrowser: true });
@@ -443,38 +486,38 @@ async function runCliSetup(options) {
443
486
  // Main menu loop - keeps returning to menu until user exits
444
487
  while (true) {
445
488
  const setupMode = await select({
446
- message: 'What would you like to do?',
489
+ message: t('menu.prompt'),
447
490
  choices: [
448
491
  {
449
492
  value: 'quick',
450
- name: '⚡ Quick Setup (5 minutes)',
451
- description: 'Deploy Authrim with minimal configuration',
493
+ name: `⚡ ${t('menu.quick')}`,
494
+ description: t('menu.quickDesc'),
452
495
  },
453
496
  {
454
497
  value: 'normal',
455
- name: '🔧 Custom Setup',
456
- description: 'Configure all options step by step',
498
+ name: `🔧 ${t('menu.custom')}`,
499
+ description: t('menu.customDesc'),
457
500
  },
458
501
  {
459
502
  value: 'manage',
460
- name: '📋 View Existing Environments',
461
- description: 'View, inspect, or delete existing environments',
503
+ name: `📋 ${t('menu.manage')}`,
504
+ description: t('menu.manageDesc'),
462
505
  },
463
506
  {
464
507
  value: 'load',
465
- name: '📂 Load Existing Configuration',
466
- description: 'Resume setup from authrim-config.json',
508
+ name: `📂 ${t('menu.load')}`,
509
+ description: t('menu.loadDesc'),
467
510
  },
468
511
  {
469
512
  value: 'exit',
470
- name: '❌ Exit',
471
- description: 'Exit setup',
513
+ name: `❌ ${t('menu.exit')}`,
514
+ description: t('menu.exitDesc'),
472
515
  },
473
516
  ],
474
517
  });
475
518
  if (setupMode === 'exit') {
476
519
  console.log('');
477
- console.log(chalk.gray('Goodbye!'));
520
+ console.log(chalk.gray(t('menu.goodbye')));
478
521
  console.log('');
479
522
  break;
480
523
  }
@@ -788,60 +831,63 @@ async function runLoadConfig() {
788
831
  async function runQuickSetup(options) {
789
832
  console.log('');
790
833
  console.log(chalk.blue('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
791
- console.log(chalk.bold('⚡ Quick Setup'));
834
+ console.log(chalk.bold(t('quick.title')));
792
835
  console.log(chalk.blue('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
793
836
  console.log('');
794
837
  // Step 1: Environment prefix
795
838
  const envPrefix = await input({
796
- message: 'Enter environment name',
839
+ message: t('env.prompt'),
797
840
  default: options.env || 'prod',
798
841
  validate: (value) => {
799
842
  if (!/^[a-z][a-z0-9-]*$/.test(value)) {
800
- return 'Only lowercase alphanumeric and hyphens allowed (e.g., prod, staging, dev)';
843
+ return t('env.customValidation');
801
844
  }
802
845
  return true;
803
846
  },
804
847
  });
805
848
  // Check if environment already exists
806
- const checkSpinner = ora('Checking for existing environments...').start();
849
+ const checkSpinner = ora(t('env.checking')).start();
807
850
  try {
808
851
  const existingEnvs = await detectEnvironments();
809
852
  const existingEnv = existingEnvs.find((e) => e.env === envPrefix);
810
853
  if (existingEnv) {
811
- checkSpinner.fail(`Environment "${envPrefix}" already exists`);
854
+ checkSpinner.fail(t('env.alreadyExists', { env: envPrefix }));
812
855
  console.log('');
813
- console.log(chalk.yellow(' Existing resources:'));
814
- console.log(` Workers: ${existingEnv.workers.length}`);
815
- console.log(` D1 Databases: ${existingEnv.d1.length}`);
816
- console.log(` KV Namespaces: ${existingEnv.kv.length}`);
856
+ console.log(chalk.yellow(' ' + t('env.existingResources')));
857
+ console.log(` ${t('env.workers', { count: String(existingEnv.workers.length) })}`);
858
+ console.log(` ${t('env.d1Databases', { count: String(existingEnv.d1.length) })}`);
859
+ console.log(` ${t('env.kvNamespaces', { count: String(existingEnv.kv.length) })}`);
817
860
  console.log('');
818
- console.log(chalk.yellow(' Please choose a different name or use "npx @authrim/setup manage" to delete it first.'));
861
+ console.log(chalk.yellow(' ' + t('env.chooseAnother')));
819
862
  return;
820
863
  }
821
- checkSpinner.succeed('Environment name is available');
864
+ checkSpinner.succeed(t('env.available'));
822
865
  }
823
866
  catch {
824
- checkSpinner.warn('Could not check existing environments (continuing anyway)');
867
+ checkSpinner.warn(t('env.checkFailed'));
825
868
  }
826
869
  // Step 2: Cloudflare API Token
827
870
  const cfApiToken = await password({
828
- message: 'Enter Cloudflare API Token',
871
+ message: t('cf.apiTokenPrompt'),
829
872
  mask: '*',
830
873
  validate: (value) => {
831
874
  if (!value || value.length < 10) {
832
- return 'Please enter a valid API Token';
875
+ return t('cf.apiTokenValidation');
833
876
  }
834
877
  return true;
835
878
  },
836
879
  });
837
880
  // Step 3: Show infrastructure info
838
881
  console.log('');
839
- console.log(chalk.gray(' Workers to deploy: ' + envPrefix + '-ar-router, ' + envPrefix + '-ar-auth, ...'));
840
- console.log(chalk.gray(' Default API: ' + getWorkersDevUrl(envPrefix + '-ar-router')));
882
+ console.log(chalk.gray(' ' +
883
+ t('infra.workersToDeploy', {
884
+ workers: envPrefix + '-ar-router, ' + envPrefix + '-ar-auth, ...',
885
+ })));
886
+ console.log(chalk.gray(' ' + t('infra.defaultApi', { url: getWorkersDevUrl(envPrefix + '-ar-router') })));
841
887
  console.log('');
842
888
  // Step 4: Domain configuration (single-tenant mode only in Quick Setup)
843
889
  const useCustomDomain = await confirm({
844
- message: 'Configure custom domain? (for Issuer URL)',
890
+ message: t('domain.prompt'),
845
891
  default: false,
846
892
  });
847
893
  let apiDomain = null;
@@ -849,55 +895,59 @@ async function runQuickSetup(options) {
849
895
  let adminUiDomain = null;
850
896
  if (useCustomDomain) {
851
897
  console.log('');
852
- console.log(chalk.gray(' In single-tenant mode, Issuer URL = API domain'));
898
+ console.log(chalk.gray(' ' + t('domain.singleTenantNote')));
853
899
  console.log('');
854
900
  apiDomain = await input({
855
- message: 'API / Issuer domain (e.g., auth.example.com)',
901
+ message: t('domain.apiDomain'),
856
902
  validate: (value) => {
857
903
  if (!value)
858
904
  return true; // Allow empty for workers.dev fallback
859
905
  if (!/^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/.test(value)) {
860
- return 'Please enter a valid domain (e.g., auth.example.com)';
906
+ return t('domain.customValidation');
861
907
  }
862
908
  return true;
863
909
  },
864
910
  });
865
911
  loginUiDomain = await input({
866
- message: 'Login UI domain (Enter to skip)',
912
+ message: t('domain.loginUiDomain'),
867
913
  default: '',
868
914
  });
869
915
  adminUiDomain = await input({
870
- message: 'Admin UI domain (Enter to skip)',
916
+ message: t('domain.adminUiDomain'),
871
917
  default: '',
872
918
  });
873
919
  }
874
920
  // Database Configuration
875
921
  console.log('');
876
- console.log(chalk.blue('━━━ Database Configuration ━━━'));
877
- console.log(chalk.yellow('⚠️ Database region cannot be changed after creation.'));
922
+ console.log(chalk.blue('━━━ ' + t('db.title') + ' ━━━'));
923
+ console.log(chalk.yellow('⚠️ ' + t('db.regionWarning')));
878
924
  console.log('');
879
925
  const locationChoices = [
880
- { name: 'Automatic (nearest to you)', value: 'auto' },
881
- { name: '── Location Hints ──', value: '__separator1__', disabled: true },
882
- { name: 'North America (West)', value: 'wnam' },
883
- { name: 'North America (East)', value: 'enam' },
884
- { name: 'Europe (West)', value: 'weur' },
885
- { name: 'Europe (East)', value: 'eeur' },
886
- { name: 'Asia Pacific', value: 'apac' },
887
- { name: 'Oceania', value: 'oc' },
888
- { name: '── Jurisdiction (Compliance) ──', value: '__separator2__', disabled: true },
889
- { name: 'EU Jurisdiction (GDPR compliance)', value: 'eu' },
926
+ { name: t('region.auto'), value: 'auto' },
927
+ { name: '── ' + t('db.locationHints') + ' ──', value: '__separator1__', disabled: true },
928
+ { name: t('region.wnam'), value: 'wnam' },
929
+ { name: t('region.enam'), value: 'enam' },
930
+ { name: t('region.weur'), value: 'weur' },
931
+ { name: t('region.eeur'), value: 'eeur' },
932
+ { name: t('region.apac'), value: 'apac' },
933
+ { name: t('region.oceania'), value: 'oc' },
934
+ {
935
+ name: '── ' + t('db.jurisdictionCompliance') + ' ──',
936
+ value: '__separator2__',
937
+ disabled: true,
938
+ },
939
+ { name: t('region.euJurisdiction'), value: 'eu' },
890
940
  ];
891
- console.log(chalk.gray(' Core DB: Stores OAuth clients, tokens, sessions, audit logs'));
941
+ console.log(chalk.gray(' ' + t('db.coreDescription')));
892
942
  const coreDbLocation = await select({
893
- message: 'Core Database region',
943
+ message: t('db.coreRegion'),
894
944
  choices: locationChoices,
895
945
  default: 'auto',
896
946
  });
897
947
  console.log('');
898
- console.log(chalk.gray(' PII DB: Stores user profiles, credentials, personal data'));
948
+ console.log(chalk.gray(' ' + t('db.piiDescription')));
899
949
  const piiDbLocation = await select({
900
- message: 'PII Database region',
950
+ message: t('db.piiRegion'),
901
951
  choices: locationChoices,
902
952
  default: 'auto',
903
953
  });
@@ -913,43 +963,43 @@ async function runQuickSetup(options) {
913
963
  }
914
964
  // Email Provider Configuration
915
965
  console.log('');
916
- console.log(chalk.blue('━━━ Email Provider ━━━'));
917
- console.log(chalk.gray('Configure email sending for magic links and verification codes.'));
966
+ console.log(chalk.blue('━━━ ' + t('email.title') + ' ━━━'));
967
+ console.log(chalk.gray(t('email.description')));
918
968
  console.log('');
919
969
  const configureEmail = await confirm({
920
- message: 'Configure email provider now?',
970
+ message: t('email.prompt'),
921
971
  default: false,
922
972
  });
923
973
  let emailConfig = { provider: 'none' };
924
974
  if (configureEmail) {
925
975
  console.log('');
926
- console.log(chalk.gray('Resend is the supported email provider.'));
927
- console.log(chalk.gray('Get your API key at: https://resend.com/api-keys'));
928
- console.log(chalk.gray('Set up domain at: https://resend.com/domains'));
976
+ console.log(chalk.gray(t('email.resendDesc')));
977
+ console.log(chalk.gray(t('email.apiKeyHint')));
978
+ console.log(chalk.gray(t('email.domainHint')));
929
979
  console.log('');
930
980
  const resendApiKey = await password({
931
- message: 'Resend API key',
981
+ message: t('email.apiKeyPrompt'),
932
982
  mask: '*',
933
983
  validate: (value) => {
934
984
  if (!value.trim())
935
- return 'API key is required';
985
+ return t('email.apiKeyRequired');
936
986
  if (!value.startsWith('re_')) {
937
- return 'Warning: Resend API keys typically start with "re_"';
987
+ return t('email.apiKeyWarning');
938
988
  }
939
989
  return true;
940
990
  },
941
991
  });
942
992
  const fromAddress = await input({
943
- message: 'From email address',
993
+ message: t('email.fromAddressPrompt'),
944
994
  default: 'noreply@yourdomain.com',
945
995
  validate: (value) => {
946
996
  if (!value.includes('@'))
947
- return 'Please enter a valid email address';
997
+ return t('email.fromAddressValidation');
948
998
  return true;
949
999
  },
950
1000
  });
951
1001
  const fromName = await input({
952
- message: 'From display name (optional)',
1002
+ message: t('email.fromNamePrompt'),
953
1003
  default: 'Authrim',
954
1004
  });
955
1005
  emailConfig = {
@@ -959,8 +1009,8 @@ async function runQuickSetup(options) {
959
1009
  apiKey: resendApiKey,
960
1010
  };
961
1011
  console.log('');
962
- console.log(chalk.yellow('⚠️ Domain verification required for sending from your own domain.'));
963
- console.log(chalk.gray(' See: https://resend.com/docs/dashboard/domains/introduction'));
1012
+ console.log(chalk.yellow('⚠️ ' + t('email.domainVerificationRequired')));
1013
+ console.log(chalk.gray(' ' + t('email.seeDocumentation')));
964
1014
  }
965
1015
  // Create configuration
966
1016
  const config = createDefaultConfig(envPrefix);
@@ -1050,94 +1100,94 @@ async function runQuickSetup(options) {
1050
1100
  async function runNormalSetup(options) {
1051
1101
  console.log('');
1052
1102
  console.log(chalk.blue('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
1053
- console.log(chalk.bold('🔧 Custom Setup'));
1103
+ console.log(chalk.bold(t('custom.title')));
1054
1104
  console.log(chalk.blue('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
1055
1105
  console.log('');
1056
1106
  // Step 1: Environment prefix
1057
1107
  const envPrefix = await input({
1058
- message: 'Enter environment name',
1108
+ message: t('env.prompt'),
1059
1109
  default: options.env || 'prod',
1060
1110
  validate: (value) => {
1061
1111
  if (!/^[a-z][a-z0-9-]*$/.test(value)) {
1062
- return 'Only lowercase alphanumeric and hyphens allowed (e.g., prod, staging, dev)';
1112
+ return t('env.customValidation');
1063
1113
  }
1064
1114
  return true;
1065
1115
  },
1066
1116
  });
1067
1117
  // Check if environment already exists
1068
- const checkSpinner = ora('Checking for existing environments...').start();
1118
+ const checkSpinner = ora(t('env.checking')).start();
1069
1119
  try {
1070
1120
  const existingEnvs = await detectEnvironments();
1071
1121
  const existingEnv = existingEnvs.find((e) => e.env === envPrefix);
1072
1122
  if (existingEnv) {
1073
- checkSpinner.fail(`Environment "${envPrefix}" already exists`);
1123
+ checkSpinner.fail(t('env.alreadyExists', { env: envPrefix }));
1074
1124
  console.log('');
1075
- console.log(chalk.yellow(' Existing resources:'));
1076
- console.log(` Workers: ${existingEnv.workers.length}`);
1077
- console.log(` D1 Databases: ${existingEnv.d1.length}`);
1078
- console.log(` KV Namespaces: ${existingEnv.kv.length}`);
1125
+ console.log(chalk.yellow(' ' + t('env.existingResources')));
1126
+ console.log(` ${t('env.workers', { count: String(existingEnv.workers.length) })}`);
1127
+ console.log(` ${t('env.d1Databases', { count: String(existingEnv.d1.length) })}`);
1128
+ console.log(` ${t('env.kvNamespaces', { count: String(existingEnv.kv.length) })}`);
1079
1129
  console.log('');
1080
- console.log(chalk.yellow(' Please choose a different name or use "npx @authrim/setup manage" to delete it first.'));
1130
+ console.log(chalk.yellow(' ' + t('env.chooseAnother')));
1081
1131
  return;
1082
1132
  }
1083
- checkSpinner.succeed('Environment name is available');
1133
+ checkSpinner.succeed(t('env.available'));
1084
1134
  }
1085
1135
  catch {
1086
- checkSpinner.warn('Could not check existing environments (continuing anyway)');
1136
+ checkSpinner.warn(t('env.checkFailed'));
1087
1137
  }
1088
1138
  // Step 2: Cloudflare API Token
1089
1139
  const cfApiToken = await password({
1090
- message: 'Enter Cloudflare API Token',
1140
+ message: t('cf.apiTokenPrompt'),
1091
1141
  mask: '*',
1092
1142
  validate: (value) => {
1093
1143
  if (!value || value.length < 10) {
1094
- return 'Please enter a valid API Token';
1144
+ return t('cf.apiTokenValidation');
1095
1145
  }
1096
1146
  return true;
1097
1147
  },
1098
1148
  });
1099
1149
  // Step 3: Profile selection
1100
1150
  const profile = await select({
1101
- message: 'Select OIDC profile',
1151
+ message: t('profile.prompt'),
1102
1152
  choices: [
1103
1153
  {
1104
1154
  value: 'basic-op',
1105
- name: 'Basic OP (Standard OIDC Provider)',
1106
- description: 'Standard OIDC features',
1155
+ name: t('profile.basicOp'),
1156
+ description: t('profile.basicOpDesc'),
1107
1157
  },
1108
1158
  {
1109
1159
  value: 'fapi-rw',
1110
- name: 'FAPI Read-Write (Financial Grade)',
1111
- description: 'FAPI 1.0 Read-Write Security Profile compliant',
1160
+ name: t('profile.fapiRw'),
1161
+ description: t('profile.fapiRwDesc'),
1112
1162
  },
1113
1163
  {
1114
1164
  value: 'fapi2-security',
1115
- name: 'FAPI 2.0 Security Profile',
1116
- description: 'FAPI 2.0 Security Profile compliant (highest security)',
1165
+ name: t('profile.fapi2Security'),
1166
+ description: t('profile.fapi2SecurityDesc'),
1117
1167
  },
1118
1168
  ],
1119
1169
  default: 'basic-op',
1120
1170
  });
1121
1171
  // Step 4: Infrastructure overview (Workers are auto-generated from env)
1122
1172
  console.log('');
1123
- console.log(chalk.blue('━━━ Infrastructure (Auto-generated) ━━━'));
1173
+ console.log(chalk.blue('━━━ ' + t('infra.title') + ' ━━━'));
1124
1174
  console.log('');
1125
- console.log(chalk.gray(' The following Workers will be deployed:'));
1126
- console.log(` Router: ${chalk.cyan(envPrefix + '-ar-router')}`);
1127
- console.log(` Auth: ${chalk.cyan(envPrefix + '-ar-auth')}`);
1128
- console.log(` Token: ${chalk.cyan(envPrefix + '-ar-token')}`);
1129
- console.log(` Management: ${chalk.cyan(envPrefix + '-ar-management')}`);
1130
- console.log(chalk.gray(' ... and other supporting workers'));
1175
+ console.log(chalk.gray(' ' + t('infra.workersNote')));
1176
+ console.log(` ${t('infra.router')} ${chalk.cyan(envPrefix + '-ar-router')}`);
1177
+ console.log(` ${t('infra.auth')} ${chalk.cyan(envPrefix + '-ar-auth')}`);
1178
+ console.log(` ${t('infra.token')} ${chalk.cyan(envPrefix + '-ar-token')}`);
1179
+ console.log(` ${t('infra.management')} ${chalk.cyan(envPrefix + '-ar-management')}`);
1180
+ console.log(chalk.gray(' ' + t('infra.otherWorkers')));
1131
1181
  console.log('');
1132
- console.log(chalk.gray(' Default endpoints (without custom domain):'));
1133
- console.log(` API: ${chalk.gray(getWorkersDevUrl(envPrefix + '-ar-router'))}`);
1134
- console.log(` UI: ${chalk.gray(getPagesDevUrl(envPrefix + '-ar-ui'))}`);
1182
+ console.log(chalk.gray(' ' + t('infra.defaultEndpoints')));
1183
+ console.log(` ${t('infra.api')} ${chalk.gray(getWorkersDevUrl(envPrefix + '-ar-router'))}`);
1184
+ console.log(` ${t('infra.ui')} ${chalk.gray(getPagesDevUrl(envPrefix + '-ar-ui'))}`);
1135
1185
  console.log('');
1136
1186
  // Step 5: Tenant configuration
1137
- console.log(chalk.blue('━━━ Tenant Mode ━━━'));
1187
+ console.log(chalk.blue('━━━ ' + t('tenant.title') + ' ━━━'));
1138
1188
  console.log('');
1139
1189
  const multiTenant = await confirm({
1140
- message: 'Enable multi-tenant mode? (subdomain-based tenant isolation)',
1190
+ message: t('tenant.multiTenantPrompt'),
1141
1191
  default: false,
1142
1192
  });
1143
1193
  let tenantName = 'default';
@@ -1150,59 +1200,59 @@ async function runNormalSetup(options) {
1150
1200
  if (multiTenant) {
1151
1201
  // Multi-tenant mode: base domain is required, becomes the issuer base
1152
1202
  console.log('');
1153
- console.log(chalk.blue('━━━ Multi-tenant URL Configuration ━━━'));
1203
+ console.log(chalk.blue('━━━ ' + t('tenant.multiTenantTitle') + ' ━━━'));
1154
1204
  console.log('');
1155
- console.log(chalk.gray(' In multi-tenant mode:'));
1156
- console.log(chalk.gray(' • Each tenant has a subdomain: https://{tenant}.{base-domain}'));
1157
- console.log(chalk.gray(' • The base domain points to the router Worker'));
1158
- console.log(chalk.gray(' • Issuer URL is dynamically built from Host header'));
1205
+ console.log(chalk.gray(' ' + t('tenant.multiTenantNote1')));
1206
+ console.log(chalk.gray(' • ' + t('tenant.multiTenantNote2')));
1207
+ console.log(chalk.gray(' • ' + t('tenant.multiTenantNote3')));
1208
+ console.log(chalk.gray(' • ' + t('tenant.multiTenantNote4')));
1159
1209
  console.log('');
1160
1210
  baseDomain = await input({
1161
- message: 'Base domain (e.g., authrim.com)',
1211
+ message: t('tenant.baseDomainPrompt'),
1162
1212
  validate: (value) => {
1163
1213
  if (!value)
1164
- return 'Base domain is required for multi-tenant mode';
1214
+ return t('tenant.baseDomainRequired');
1165
1215
  if (!/^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/.test(value)) {
1166
- return 'Please enter a valid domain (e.g., authrim.com)';
1216
+ return t('tenant.baseDomainValidation');
1167
1217
  }
1168
1218
  return true;
1169
1219
  },
1170
1220
  });
1171
1221
  console.log('');
1172
- console.log(chalk.green(' ✓ Issuer URL format: https://{tenant}.' + baseDomain));
1173
- console.log(chalk.gray(' Example: https://acme.' + baseDomain));
1222
+ console.log(chalk.green(' ✓ ' + t('tenant.issuerFormat', { domain: baseDomain })));
1223
+ console.log(chalk.gray(' ' + t('tenant.issuerExample', { domain: baseDomain })));
1174
1224
  console.log('');
1175
1225
  // API domain in multi-tenant is the base domain (or custom apex)
1176
1226
  apiDomain = baseDomain;
1177
1227
  tenantName = await input({
1178
- message: 'Default tenant name (identifier)',
1228
+ message: t('tenant.defaultTenantPrompt'),
1179
1229
  default: 'default',
1180
1230
  validate: (value) => {
1181
1231
  if (!/^[a-z][a-z0-9-]*$/.test(value)) {
1182
- return 'Only lowercase alphanumeric and hyphens allowed';
1232
+ return t('tenant.defaultTenantValidation');
1183
1233
  }
1184
1234
  return true;
1185
1235
  },
1186
1236
  });
1187
1237
  tenantDisplayName = await input({
1188
- message: 'Default tenant display name',
1238
+ message: t('tenant.displayNamePrompt'),
1189
1239
  default: 'Default Tenant',
1190
1240
  });
1191
1241
  // UI domains for multi-tenant
1192
1242
  console.log('');
1193
- console.log(chalk.blue('━━━ UI Domain Configuration ━━━'));
1243
+ console.log(chalk.blue('━━━ ' + t('tenant.uiDomainTitle') + ' ━━━'));
1194
1244
  console.log('');
1195
1245
  const useCustomUiDomain = await confirm({
1196
- message: 'Configure custom UI domains?',
1246
+ message: t('tenant.customUiDomainPrompt'),
1197
1247
  default: false,
1198
1248
  });
1199
1249
  if (useCustomUiDomain) {
1200
1250
  loginUiDomain = await input({
1201
- message: 'Login UI domain (e.g., login.example.com)',
1251
+ message: t('tenant.loginUiDomain'),
1202
1252
  default: '',
1203
1253
  });
1204
1254
  adminUiDomain = await input({
1205
- message: 'Admin UI domain (e.g., admin.example.com)',
1255
+ message: t('tenant.adminUiDomain'),
1206
1256
  default: '',
1207
1257
  });
1208
1258
  }
@@ -1210,86 +1260,86 @@ async function runNormalSetup(options) {
1210
1260
  else {
1211
1261
  // Single-tenant mode
1212
1262
  console.log('');
1213
- console.log(chalk.blue('━━━ Single-tenant URL Configuration ━━━'));
1263
+ console.log(chalk.blue('━━━ ' + t('tenant.singleTenantTitle') + ' ━━━'));
1214
1264
  console.log('');
1215
- console.log(chalk.gray(' In single-tenant mode:'));
1216
- console.log(chalk.gray(' • Issuer URL = API custom domain (or workers.dev fallback)'));
1217
- console.log(chalk.gray(' • All clients share the same issuer'));
1265
+ console.log(chalk.gray(' ' + t('tenant.singleTenantNote1')));
1266
+ console.log(chalk.gray(' • ' + t('tenant.singleTenantNote2')));
1267
+ console.log(chalk.gray(' • ' + t('tenant.singleTenantNote3')));
1218
1268
  console.log('');
1219
1269
  tenantDisplayName = await input({
1220
- message: 'Organization name (display name)',
1270
+ message: t('tenant.organizationName'),
1221
1271
  default: 'Default Tenant',
1222
1272
  });
1223
1273
  const useCustomDomain = await confirm({
1224
- message: 'Configure custom domain?',
1274
+ message: t('domain.prompt'),
1225
1275
  default: false,
1226
1276
  });
1227
1277
  if (useCustomDomain) {
1228
1278
  console.log('');
1229
- console.log(chalk.gray(' Enter custom domains (leave empty to use Cloudflare defaults)'));
1279
+ console.log(chalk.gray(' ' + t('domain.enterDomains')));
1230
1280
  console.log('');
1231
1281
  apiDomain = await input({
1232
- message: 'API / Issuer domain (e.g., auth.example.com)',
1282
+ message: t('domain.apiDomain'),
1233
1283
  validate: (value) => {
1234
1284
  if (!value)
1235
1285
  return true;
1236
1286
  if (!/^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/.test(value)) {
1237
- return 'Please enter a valid domain';
1287
+ return t('domain.customValidation');
1238
1288
  }
1239
1289
  return true;
1240
1290
  },
1241
1291
  });
1242
1292
  loginUiDomain = await input({
1243
- message: 'Login UI domain (Enter to skip)',
1293
+ message: t('domain.loginUiDomain'),
1244
1294
  default: '',
1245
1295
  });
1246
1296
  adminUiDomain = await input({
1247
- message: 'Admin UI domain (Enter to skip)',
1297
+ message: t('domain.adminUiDomain'),
1248
1298
  default: '',
1249
1299
  });
1250
1300
  }
1251
1301
  if (apiDomain) {
1252
1302
  console.log('');
1253
- console.log(chalk.green(' ✓ Issuer URL: https://' + apiDomain));
1303
+ console.log(chalk.green(' ✓ ' + t('domain.issuerUrl', { url: 'https://' + apiDomain })));
1254
1304
  }
1255
1305
  else {
1256
1306
  console.log('');
1257
- console.log(chalk.green(' ✓ Issuer URL: ' + getWorkersDevUrl(envPrefix + '-ar-router')));
1258
- console.log(chalk.gray(' (using Cloudflare workers.dev domain)'));
1307
+ console.log(chalk.green(' ✓ ' + t('domain.issuerUrl', { url: getWorkersDevUrl(envPrefix + '-ar-router') })));
1308
+ console.log(chalk.gray(' ' + t('domain.usingWorkersDev')));
1259
1309
  }
1260
1310
  }
1261
1311
  // Step 5: Optional components
1262
1312
  console.log('');
1263
- console.log(chalk.blue('━━━ Optional Components ━━━'));
1264
- console.log(chalk.gray(' Note: Social Login and Policy Engine are standard components'));
1313
+ console.log(chalk.blue('━━━ ' + t('components.title') + ' ━━━'));
1314
+ console.log(chalk.gray(' ' + t('components.note')));
1265
1315
  console.log('');
1266
1316
  const enableSaml = await confirm({
1267
- message: 'Enable SAML support?',
1317
+ message: t('components.samlPrompt'),
1268
1318
  default: false,
1269
1319
  });
1270
1320
  const enableVc = await confirm({
1271
- message: 'Enable Verifiable Credentials?',
1321
+ message: t('components.vcPrompt'),
1272
1322
  default: false,
1273
1323
  });
1274
1324
  // Step 6: Feature flags
1275
1325
  console.log('');
1276
- console.log(chalk.blue('━━━ Feature Flags ━━━'));
1326
+ console.log(chalk.blue('━━━ ' + t('features.title') + ' ━━━'));
1277
1327
  console.log('');
1278
1328
  const enableQueue = await confirm({
1279
- message: 'Enable Cloudflare Queues? (for audit logs)',
1329
+ message: t('features.queuePrompt'),
1280
1330
  default: false,
1281
1331
  });
1282
1332
  const enableR2 = await confirm({
1283
- message: 'Enable Cloudflare R2? (for avatars)',
1333
+ message: t('features.r2Prompt'),
1284
1334
  default: false,
1285
1335
  });
1286
1336
  const emailProviderChoice = await select({
1287
- message: 'Select email provider',
1337
+ message: t('email.title'),
1288
1338
  choices: [
1289
- { value: 'none', name: 'None (configure later)' },
1290
- { value: 'resend', name: 'Resend' },
1339
+ { value: 'none', name: t('email.skipOption') },
1340
+ { value: 'resend', name: t('email.resendOption') },
1291
1341
  { value: 'sendgrid', name: 'SendGrid (coming soon)', disabled: true },
1292
- { value: 'ses', name: 'AWS SES (coming soon)', disabled: true },
1342
+ { value: 'ses', name: t('email.sesOption') + ' (coming soon)', disabled: true },
1293
1343
  ],
1294
1344
  default: 'none',
1295
1345
  });
@@ -1297,33 +1347,33 @@ async function runNormalSetup(options) {
1297
1347
  let emailConfigNormal = { provider: 'none' };
1298
1348
  if (emailProviderChoice === 'resend') {
1299
1349
  console.log('');
1300
- console.log(chalk.blue('━━━ Resend Configuration ━━━'));
1301
- console.log(chalk.gray('Get your API key at: https://resend.com/api-keys'));
1302
- console.log(chalk.gray('Set up domain at: https://resend.com/domains'));
1350
+ console.log(chalk.blue('━━━ ' + t('email.resendOption') + ' ━━━'));
1351
+ console.log(chalk.gray(t('email.apiKeyHint')));
1352
+ console.log(chalk.gray(t('email.domainHint')));
1303
1353
  console.log('');
1304
1354
  const resendApiKey = await password({
1305
- message: 'Resend API key',
1355
+ message: t('email.apiKeyPrompt'),
1306
1356
  mask: '*',
1307
1357
  validate: (value) => {
1308
1358
  if (!value.trim())
1309
- return 'API key is required';
1359
+ return t('email.apiKeyRequired');
1310
1360
  if (!value.startsWith('re_')) {
1311
- return 'Warning: Resend API keys typically start with "re_"';
1361
+ return t('email.apiKeyWarning');
1312
1362
  }
1313
1363
  return true;
1314
1364
  },
1315
1365
  });
1316
1366
  const fromAddress = await input({
1317
- message: 'From email address',
1367
+ message: t('email.fromAddressPrompt'),
1318
1368
  default: 'noreply@yourdomain.com',
1319
1369
  validate: (value) => {
1320
1370
  if (!value.includes('@'))
1321
- return 'Please enter a valid email address';
1371
+ return t('email.fromAddressValidation');
1322
1372
  return true;
1323
1373
  },
1324
1374
  });
1325
1375
  const fromName = await input({
1326
- message: 'From display name (optional)',
1376
+ message: t('email.fromNamePrompt'),
1327
1377
  default: 'Authrim',
1328
1378
  });
1329
1379
  emailConfigNormal = {
@@ -1333,12 +1383,12 @@ async function runNormalSetup(options) {
1333
1383
  apiKey: resendApiKey,
1334
1384
  };
1335
1385
  console.log('');
1336
- console.log(chalk.yellow('⚠️ Domain verification required for sending from your own domain.'));
1337
- console.log(chalk.gray(' See: https://resend.com/docs/dashboard/domains/introduction'));
1386
+ console.log(chalk.yellow('⚠️ ' + t('email.domainVerificationRequired')));
1387
+ console.log(chalk.gray(' ' + t('email.seeDocumentation')));
1338
1388
  }
1339
1389
  // Step 7: Advanced OIDC settings
1340
1390
  const configureOidc = await confirm({
1341
- message: 'Configure OIDC settings? (token TTL, etc.)',
1391
+ message: t('oidc.configurePrompt'),
1342
1392
  default: false,
1343
1393
  });
1344
1394
  let accessTokenTtl = 3600; // 1 hour
@@ -1347,76 +1397,76 @@ async function runNormalSetup(options) {
1347
1397
  let pkceRequired = true;
1348
1398
  if (configureOidc) {
1349
1399
  console.log('');
1350
- console.log(chalk.blue('━━━ OIDC Settings ━━━'));
1400
+ console.log(chalk.blue('━━━ ' + t('oidc.title') + ' ━━━'));
1351
1401
  console.log('');
1352
1402
  const accessTokenTtlStr = await input({
1353
- message: 'Access Token TTL (sec)',
1403
+ message: t('oidc.accessTokenTtl'),
1354
1404
  default: '3600',
1355
1405
  validate: (value) => {
1356
1406
  const num = parseInt(value, 10);
1357
1407
  if (isNaN(num) || num <= 0)
1358
- return 'Please enter a positive integer';
1408
+ return t('oidc.positiveInteger');
1359
1409
  return true;
1360
1410
  },
1361
1411
  });
1362
1412
  accessTokenTtl = parseInt(accessTokenTtlStr, 10);
1363
1413
  const refreshTokenTtlStr = await input({
1364
- message: 'Refresh Token TTL (sec)',
1414
+ message: t('oidc.refreshTokenTtl'),
1365
1415
  default: '604800',
1366
1416
  validate: (value) => {
1367
1417
  const num = parseInt(value, 10);
1368
1418
  if (isNaN(num) || num <= 0)
1369
- return 'Please enter a positive integer';
1419
+ return t('oidc.positiveInteger');
1370
1420
  return true;
1371
1421
  },
1372
1422
  });
1373
1423
  refreshTokenTtl = parseInt(refreshTokenTtlStr, 10);
1374
1424
  const authCodeTtlStr = await input({
1375
- message: 'Authorization Code TTL (sec)',
1425
+ message: t('oidc.authCodeTtl'),
1376
1426
  default: '600',
1377
1427
  validate: (value) => {
1378
1428
  const num = parseInt(value, 10);
1379
1429
  if (isNaN(num) || num <= 0)
1380
- return 'Please enter a positive integer';
1430
+ return t('oidc.positiveInteger');
1381
1431
  return true;
1382
1432
  },
1383
1433
  });
1384
1434
  authCodeTtl = parseInt(authCodeTtlStr, 10);
1385
1435
  pkceRequired = await confirm({
1386
- message: 'Require PKCE?',
1436
+ message: t('oidc.pkceRequired'),
1387
1437
  default: true,
1388
1438
  });
1389
1439
  }
1390
1440
  // Step 8: Sharding settings
1391
1441
  const configureSharding = await confirm({
1392
- message: 'Configure sharding? (for high-load environments)',
1442
+ message: t('sharding.configurePrompt'),
1393
1443
  default: false,
1394
1444
  });
1395
1445
  let authCodeShards = 64;
1396
1446
  let refreshTokenShards = 8;
1397
1447
  if (configureSharding) {
1398
1448
  console.log('');
1399
- console.log(chalk.blue('━━━ Sharding Settings ━━━'));
1400
- console.log(chalk.gray(' Note: Power of 2 recommended for shard count (8, 16, 32, 64, 128)'));
1449
+ console.log(chalk.blue('━━━ ' + t('sharding.title') + ' ━━━'));
1450
+ console.log(chalk.gray(' ' + t('sharding.note')));
1401
1451
  console.log('');
1402
1452
  const authCodeShardsStr = await input({
1403
- message: 'Auth Code shard count',
1453
+ message: t('sharding.authCodeShards'),
1404
1454
  default: '64',
1405
1455
  validate: (value) => {
1406
1456
  const num = parseInt(value, 10);
1407
1457
  if (isNaN(num) || num <= 0)
1408
- return 'Please enter a positive integer';
1458
+ return t('oidc.positiveInteger');
1409
1459
  return true;
1410
1460
  },
1411
1461
  });
1412
1462
  authCodeShards = parseInt(authCodeShardsStr, 10);
1413
1463
  const refreshTokenShardsStr = await input({
1414
- message: 'Refresh Token shard count',
1464
+ message: t('sharding.refreshTokenShards'),
1415
1465
  default: '8',
1416
1466
  validate: (value) => {
1417
1467
  const num = parseInt(value, 10);
1418
1468
  if (isNaN(num) || num <= 0)
1419
- return 'Please enter a positive integer';
1469
+ return t('oidc.positiveInteger');
1420
1470
  return true;
1421
1471
  },
1422
1472
  });
@@ -1424,32 +1474,36 @@ async function runNormalSetup(options) {
1424
1474
  }
1425
1475
  // Step 9: Database Configuration
1426
1476
  console.log('');
1427
- console.log(chalk.blue('━━━ Database Configuration ━━━'));
1428
- console.log(chalk.yellow('⚠️ Database region cannot be changed after creation.'));
1477
+ console.log(chalk.blue('━━━ ' + t('db.title') + ' ━━━'));
1478
+ console.log(chalk.yellow('⚠️ ' + t('db.regionWarning')));
1429
1479
  console.log('');
1430
1480
  const dbLocationChoices = [
1431
- { name: 'Automatic (nearest to you)', value: 'auto' },
1432
- { name: '── Location Hints ──', value: '__separator1__', disabled: true },
1433
- { name: 'North America (West)', value: 'wnam' },
1434
- { name: 'North America (East)', value: 'enam' },
1435
- { name: 'Europe (West)', value: 'weur' },
1436
- { name: 'Europe (East)', value: 'eeur' },
1437
- { name: 'Asia Pacific', value: 'apac' },
1438
- { name: 'Oceania', value: 'oc' },
1439
- { name: '── Jurisdiction (Compliance) ──', value: '__separator2__', disabled: true },
1440
- { name: 'EU Jurisdiction (GDPR compliance)', value: 'eu' },
1481
+ { name: t('region.auto'), value: 'auto' },
1482
+ { name: '── ' + t('db.locationHints') + ' ──', value: '__separator1__', disabled: true },
1483
+ { name: t('region.wnam'), value: 'wnam' },
1484
+ { name: t('region.enam'), value: 'enam' },
1485
+ { name: t('region.weur'), value: 'weur' },
1486
+ { name: t('region.eeur'), value: 'eeur' },
1487
+ { name: t('region.apac'), value: 'apac' },
1488
+ { name: t('region.oceania'), value: 'oc' },
1489
+ {
1490
+ name: '── ' + t('db.jurisdictionCompliance') + ' ──',
1491
+ value: '__separator2__',
1492
+ disabled: true,
1493
+ },
1494
+ { name: t('region.euJurisdiction'), value: 'eu' },
1441
1495
  ];
1442
- console.log(chalk.gray(' Core DB: Stores OAuth clients, tokens, sessions, audit logs'));
1496
+ console.log(chalk.gray(' ' + t('db.coreDescription')));
1443
1497
  const coreDbLocation = await select({
1444
- message: 'Core Database region',
1498
+ message: t('db.coreRegion'),
1445
1499
  choices: dbLocationChoices,
1446
1500
  default: 'auto',
1447
1501
  });
1448
1502
  console.log('');
1449
- console.log(chalk.gray(' PII DB: Stores user profiles, credentials, personal data'));
1450
- console.log(chalk.gray(' Consider your data protection requirements.'));
1503
+ console.log(chalk.gray(' ' + t('db.piiDescription')));
1504
+ console.log(chalk.gray(' ' + t('db.piiNote')));
1451
1505
  const piiDbLocation = await select({
1452
- message: 'PII Database region',
1506
+ message: t('db.piiRegion'),
1453
1507
  choices: dbLocationChoices,
1454
1508
  default: 'auto',
1455
1509
  });