@authrim/setup 0.1.82 → 0.1.85

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.
@@ -31,7 +31,7 @@ async function selectLanguage() {
31
31
  message: 'Select language / 言語を選択 / 选择语言',
32
32
  choices: locales.map((l) => ({
33
33
  value: l.code,
34
- name: `${l.flag || ''} ${l.nativeName}`.trim(),
34
+ name: l.nativeName,
35
35
  })),
36
36
  });
37
37
  return locale;
@@ -831,60 +831,63 @@ async function runLoadConfig() {
831
831
  async function runQuickSetup(options) {
832
832
  console.log('');
833
833
  console.log(chalk.blue('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
834
- console.log(chalk.bold('⚡ Quick Setup'));
834
+ console.log(chalk.bold(t('quick.title')));
835
835
  console.log(chalk.blue('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
836
836
  console.log('');
837
837
  // Step 1: Environment prefix
838
838
  const envPrefix = await input({
839
- message: 'Enter environment name',
839
+ message: t('env.prompt'),
840
840
  default: options.env || 'prod',
841
841
  validate: (value) => {
842
842
  if (!/^[a-z][a-z0-9-]*$/.test(value)) {
843
- return 'Only lowercase alphanumeric and hyphens allowed (e.g., prod, staging, dev)';
843
+ return t('env.customValidation');
844
844
  }
845
845
  return true;
846
846
  },
847
847
  });
848
848
  // Check if environment already exists
849
- const checkSpinner = ora('Checking for existing environments...').start();
849
+ const checkSpinner = ora(t('env.checking')).start();
850
850
  try {
851
851
  const existingEnvs = await detectEnvironments();
852
852
  const existingEnv = existingEnvs.find((e) => e.env === envPrefix);
853
853
  if (existingEnv) {
854
- checkSpinner.fail(`Environment "${envPrefix}" already exists`);
854
+ checkSpinner.fail(t('env.alreadyExists', { env: envPrefix }));
855
855
  console.log('');
856
- console.log(chalk.yellow(' Existing resources:'));
857
- console.log(` Workers: ${existingEnv.workers.length}`);
858
- console.log(` D1 Databases: ${existingEnv.d1.length}`);
859
- 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) })}`);
860
860
  console.log('');
861
- 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')));
862
862
  return;
863
863
  }
864
- checkSpinner.succeed('Environment name is available');
864
+ checkSpinner.succeed(t('env.available'));
865
865
  }
866
866
  catch {
867
- checkSpinner.warn('Could not check existing environments (continuing anyway)');
867
+ checkSpinner.warn(t('env.checkFailed'));
868
868
  }
869
869
  // Step 2: Cloudflare API Token
870
870
  const cfApiToken = await password({
871
- message: 'Enter Cloudflare API Token',
871
+ message: t('cf.apiTokenPrompt'),
872
872
  mask: '*',
873
873
  validate: (value) => {
874
874
  if (!value || value.length < 10) {
875
- return 'Please enter a valid API Token';
875
+ return t('cf.apiTokenValidation');
876
876
  }
877
877
  return true;
878
878
  },
879
879
  });
880
880
  // Step 3: Show infrastructure info
881
881
  console.log('');
882
- console.log(chalk.gray(' Workers to deploy: ' + envPrefix + '-ar-router, ' + envPrefix + '-ar-auth, ...'));
883
- 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') })));
884
887
  console.log('');
885
888
  // Step 4: Domain configuration (single-tenant mode only in Quick Setup)
886
889
  const useCustomDomain = await confirm({
887
- message: 'Configure custom domain? (for Issuer URL)',
890
+ message: t('domain.prompt'),
888
891
  default: false,
889
892
  });
890
893
  let apiDomain = null;
@@ -892,55 +895,59 @@ async function runQuickSetup(options) {
892
895
  let adminUiDomain = null;
893
896
  if (useCustomDomain) {
894
897
  console.log('');
895
- console.log(chalk.gray(' In single-tenant mode, Issuer URL = API domain'));
898
+ console.log(chalk.gray(' ' + t('domain.singleTenantNote')));
896
899
  console.log('');
897
900
  apiDomain = await input({
898
- message: 'API / Issuer domain (e.g., auth.example.com)',
901
+ message: t('domain.apiDomain'),
899
902
  validate: (value) => {
900
903
  if (!value)
901
904
  return true; // Allow empty for workers.dev fallback
902
905
  if (!/^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/.test(value)) {
903
- return 'Please enter a valid domain (e.g., auth.example.com)';
906
+ return t('domain.customValidation');
904
907
  }
905
908
  return true;
906
909
  },
907
910
  });
908
911
  loginUiDomain = await input({
909
- message: 'Login UI domain (Enter to skip)',
912
+ message: t('domain.loginUiDomain'),
910
913
  default: '',
911
914
  });
912
915
  adminUiDomain = await input({
913
- message: 'Admin UI domain (Enter to skip)',
916
+ message: t('domain.adminUiDomain'),
914
917
  default: '',
915
918
  });
916
919
  }
917
920
  // Database Configuration
918
921
  console.log('');
919
- console.log(chalk.blue('━━━ Database Configuration ━━━'));
920
- 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')));
921
924
  console.log('');
922
925
  const locationChoices = [
923
- { name: 'Automatic (nearest to you)', value: 'auto' },
924
- { name: '── Location Hints ──', value: '__separator1__', disabled: true },
925
- { name: 'North America (West)', value: 'wnam' },
926
- { name: 'North America (East)', value: 'enam' },
927
- { name: 'Europe (West)', value: 'weur' },
928
- { name: 'Europe (East)', value: 'eeur' },
929
- { name: 'Asia Pacific', value: 'apac' },
930
- { name: 'Oceania', value: 'oc' },
931
- { name: '── Jurisdiction (Compliance) ──', value: '__separator2__', disabled: true },
932
- { 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' },
933
940
  ];
934
- console.log(chalk.gray(' Core DB: Stores OAuth clients, tokens, sessions, audit logs'));
941
+ console.log(chalk.gray(' ' + t('db.coreDescription')));
935
942
  const coreDbLocation = await select({
936
- message: 'Core Database region',
943
+ message: t('db.coreRegion'),
937
944
  choices: locationChoices,
938
945
  default: 'auto',
939
946
  });
940
947
  console.log('');
941
- console.log(chalk.gray(' PII DB: Stores user profiles, credentials, personal data'));
948
+ console.log(chalk.gray(' ' + t('db.piiDescription')));
942
949
  const piiDbLocation = await select({
943
- message: 'PII Database region',
950
+ message: t('db.piiRegion'),
944
951
  choices: locationChoices,
945
952
  default: 'auto',
946
953
  });
@@ -956,43 +963,43 @@ async function runQuickSetup(options) {
956
963
  }
957
964
  // Email Provider Configuration
958
965
  console.log('');
959
- console.log(chalk.blue('━━━ Email Provider ━━━'));
960
- 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')));
961
968
  console.log('');
962
969
  const configureEmail = await confirm({
963
- message: 'Configure email provider now?',
970
+ message: t('email.prompt'),
964
971
  default: false,
965
972
  });
966
973
  let emailConfig = { provider: 'none' };
967
974
  if (configureEmail) {
968
975
  console.log('');
969
- console.log(chalk.gray('Resend is the supported email provider.'));
970
- console.log(chalk.gray('Get your API key at: https://resend.com/api-keys'));
971
- 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')));
972
979
  console.log('');
973
980
  const resendApiKey = await password({
974
- message: 'Resend API key',
981
+ message: t('email.apiKeyPrompt'),
975
982
  mask: '*',
976
983
  validate: (value) => {
977
984
  if (!value.trim())
978
- return 'API key is required';
985
+ return t('email.apiKeyRequired');
979
986
  if (!value.startsWith('re_')) {
980
- return 'Warning: Resend API keys typically start with "re_"';
987
+ return t('email.apiKeyWarning');
981
988
  }
982
989
  return true;
983
990
  },
984
991
  });
985
992
  const fromAddress = await input({
986
- message: 'From email address',
993
+ message: t('email.fromAddressPrompt'),
987
994
  default: 'noreply@yourdomain.com',
988
995
  validate: (value) => {
989
996
  if (!value.includes('@'))
990
- return 'Please enter a valid email address';
997
+ return t('email.fromAddressValidation');
991
998
  return true;
992
999
  },
993
1000
  });
994
1001
  const fromName = await input({
995
- message: 'From display name (optional)',
1002
+ message: t('email.fromNamePrompt'),
996
1003
  default: 'Authrim',
997
1004
  });
998
1005
  emailConfig = {
@@ -1002,8 +1009,8 @@ async function runQuickSetup(options) {
1002
1009
  apiKey: resendApiKey,
1003
1010
  };
1004
1011
  console.log('');
1005
- console.log(chalk.yellow('⚠️ Domain verification required for sending from your own domain.'));
1006
- 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')));
1007
1014
  }
1008
1015
  // Create configuration
1009
1016
  const config = createDefaultConfig(envPrefix);
@@ -1093,94 +1100,94 @@ async function runQuickSetup(options) {
1093
1100
  async function runNormalSetup(options) {
1094
1101
  console.log('');
1095
1102
  console.log(chalk.blue('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
1096
- console.log(chalk.bold('🔧 Custom Setup'));
1103
+ console.log(chalk.bold(t('custom.title')));
1097
1104
  console.log(chalk.blue('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
1098
1105
  console.log('');
1099
1106
  // Step 1: Environment prefix
1100
1107
  const envPrefix = await input({
1101
- message: 'Enter environment name',
1108
+ message: t('env.prompt'),
1102
1109
  default: options.env || 'prod',
1103
1110
  validate: (value) => {
1104
1111
  if (!/^[a-z][a-z0-9-]*$/.test(value)) {
1105
- return 'Only lowercase alphanumeric and hyphens allowed (e.g., prod, staging, dev)';
1112
+ return t('env.customValidation');
1106
1113
  }
1107
1114
  return true;
1108
1115
  },
1109
1116
  });
1110
1117
  // Check if environment already exists
1111
- const checkSpinner = ora('Checking for existing environments...').start();
1118
+ const checkSpinner = ora(t('env.checking')).start();
1112
1119
  try {
1113
1120
  const existingEnvs = await detectEnvironments();
1114
1121
  const existingEnv = existingEnvs.find((e) => e.env === envPrefix);
1115
1122
  if (existingEnv) {
1116
- checkSpinner.fail(`Environment "${envPrefix}" already exists`);
1123
+ checkSpinner.fail(t('env.alreadyExists', { env: envPrefix }));
1117
1124
  console.log('');
1118
- console.log(chalk.yellow(' Existing resources:'));
1119
- console.log(` Workers: ${existingEnv.workers.length}`);
1120
- console.log(` D1 Databases: ${existingEnv.d1.length}`);
1121
- 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) })}`);
1122
1129
  console.log('');
1123
- 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')));
1124
1131
  return;
1125
1132
  }
1126
- checkSpinner.succeed('Environment name is available');
1133
+ checkSpinner.succeed(t('env.available'));
1127
1134
  }
1128
1135
  catch {
1129
- checkSpinner.warn('Could not check existing environments (continuing anyway)');
1136
+ checkSpinner.warn(t('env.checkFailed'));
1130
1137
  }
1131
1138
  // Step 2: Cloudflare API Token
1132
1139
  const cfApiToken = await password({
1133
- message: 'Enter Cloudflare API Token',
1140
+ message: t('cf.apiTokenPrompt'),
1134
1141
  mask: '*',
1135
1142
  validate: (value) => {
1136
1143
  if (!value || value.length < 10) {
1137
- return 'Please enter a valid API Token';
1144
+ return t('cf.apiTokenValidation');
1138
1145
  }
1139
1146
  return true;
1140
1147
  },
1141
1148
  });
1142
1149
  // Step 3: Profile selection
1143
1150
  const profile = await select({
1144
- message: 'Select OIDC profile',
1151
+ message: t('profile.prompt'),
1145
1152
  choices: [
1146
1153
  {
1147
1154
  value: 'basic-op',
1148
- name: 'Basic OP (Standard OIDC Provider)',
1149
- description: 'Standard OIDC features',
1155
+ name: t('profile.basicOp'),
1156
+ description: t('profile.basicOpDesc'),
1150
1157
  },
1151
1158
  {
1152
1159
  value: 'fapi-rw',
1153
- name: 'FAPI Read-Write (Financial Grade)',
1154
- description: 'FAPI 1.0 Read-Write Security Profile compliant',
1160
+ name: t('profile.fapiRw'),
1161
+ description: t('profile.fapiRwDesc'),
1155
1162
  },
1156
1163
  {
1157
1164
  value: 'fapi2-security',
1158
- name: 'FAPI 2.0 Security Profile',
1159
- description: 'FAPI 2.0 Security Profile compliant (highest security)',
1165
+ name: t('profile.fapi2Security'),
1166
+ description: t('profile.fapi2SecurityDesc'),
1160
1167
  },
1161
1168
  ],
1162
1169
  default: 'basic-op',
1163
1170
  });
1164
1171
  // Step 4: Infrastructure overview (Workers are auto-generated from env)
1165
1172
  console.log('');
1166
- console.log(chalk.blue('━━━ Infrastructure (Auto-generated) ━━━'));
1173
+ console.log(chalk.blue('━━━ ' + t('infra.title') + ' ━━━'));
1167
1174
  console.log('');
1168
- console.log(chalk.gray(' The following Workers will be deployed:'));
1169
- console.log(` Router: ${chalk.cyan(envPrefix + '-ar-router')}`);
1170
- console.log(` Auth: ${chalk.cyan(envPrefix + '-ar-auth')}`);
1171
- console.log(` Token: ${chalk.cyan(envPrefix + '-ar-token')}`);
1172
- console.log(` Management: ${chalk.cyan(envPrefix + '-ar-management')}`);
1173
- 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')));
1174
1181
  console.log('');
1175
- console.log(chalk.gray(' Default endpoints (without custom domain):'));
1176
- console.log(` API: ${chalk.gray(getWorkersDevUrl(envPrefix + '-ar-router'))}`);
1177
- 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'))}`);
1178
1185
  console.log('');
1179
1186
  // Step 5: Tenant configuration
1180
- console.log(chalk.blue('━━━ Tenant Mode ━━━'));
1187
+ console.log(chalk.blue('━━━ ' + t('tenant.title') + ' ━━━'));
1181
1188
  console.log('');
1182
1189
  const multiTenant = await confirm({
1183
- message: 'Enable multi-tenant mode? (subdomain-based tenant isolation)',
1190
+ message: t('tenant.multiTenantPrompt'),
1184
1191
  default: false,
1185
1192
  });
1186
1193
  let tenantName = 'default';
@@ -1193,59 +1200,59 @@ async function runNormalSetup(options) {
1193
1200
  if (multiTenant) {
1194
1201
  // Multi-tenant mode: base domain is required, becomes the issuer base
1195
1202
  console.log('');
1196
- console.log(chalk.blue('━━━ Multi-tenant URL Configuration ━━━'));
1203
+ console.log(chalk.blue('━━━ ' + t('tenant.multiTenantTitle') + ' ━━━'));
1197
1204
  console.log('');
1198
- console.log(chalk.gray(' In multi-tenant mode:'));
1199
- console.log(chalk.gray(' • Each tenant has a subdomain: https://{tenant}.{base-domain}'));
1200
- console.log(chalk.gray(' • The base domain points to the router Worker'));
1201
- 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')));
1202
1209
  console.log('');
1203
1210
  baseDomain = await input({
1204
- message: 'Base domain (e.g., authrim.com)',
1211
+ message: t('tenant.baseDomainPrompt'),
1205
1212
  validate: (value) => {
1206
1213
  if (!value)
1207
- return 'Base domain is required for multi-tenant mode';
1214
+ return t('tenant.baseDomainRequired');
1208
1215
  if (!/^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/.test(value)) {
1209
- return 'Please enter a valid domain (e.g., authrim.com)';
1216
+ return t('tenant.baseDomainValidation');
1210
1217
  }
1211
1218
  return true;
1212
1219
  },
1213
1220
  });
1214
1221
  console.log('');
1215
- console.log(chalk.green(' ✓ Issuer URL format: https://{tenant}.' + baseDomain));
1216
- 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 })));
1217
1224
  console.log('');
1218
1225
  // API domain in multi-tenant is the base domain (or custom apex)
1219
1226
  apiDomain = baseDomain;
1220
1227
  tenantName = await input({
1221
- message: 'Default tenant name (identifier)',
1228
+ message: t('tenant.defaultTenantPrompt'),
1222
1229
  default: 'default',
1223
1230
  validate: (value) => {
1224
1231
  if (!/^[a-z][a-z0-9-]*$/.test(value)) {
1225
- return 'Only lowercase alphanumeric and hyphens allowed';
1232
+ return t('tenant.defaultTenantValidation');
1226
1233
  }
1227
1234
  return true;
1228
1235
  },
1229
1236
  });
1230
1237
  tenantDisplayName = await input({
1231
- message: 'Default tenant display name',
1238
+ message: t('tenant.displayNamePrompt'),
1232
1239
  default: 'Default Tenant',
1233
1240
  });
1234
1241
  // UI domains for multi-tenant
1235
1242
  console.log('');
1236
- console.log(chalk.blue('━━━ UI Domain Configuration ━━━'));
1243
+ console.log(chalk.blue('━━━ ' + t('tenant.uiDomainTitle') + ' ━━━'));
1237
1244
  console.log('');
1238
1245
  const useCustomUiDomain = await confirm({
1239
- message: 'Configure custom UI domains?',
1246
+ message: t('tenant.customUiDomainPrompt'),
1240
1247
  default: false,
1241
1248
  });
1242
1249
  if (useCustomUiDomain) {
1243
1250
  loginUiDomain = await input({
1244
- message: 'Login UI domain (e.g., login.example.com)',
1251
+ message: t('tenant.loginUiDomain'),
1245
1252
  default: '',
1246
1253
  });
1247
1254
  adminUiDomain = await input({
1248
- message: 'Admin UI domain (e.g., admin.example.com)',
1255
+ message: t('tenant.adminUiDomain'),
1249
1256
  default: '',
1250
1257
  });
1251
1258
  }
@@ -1253,86 +1260,86 @@ async function runNormalSetup(options) {
1253
1260
  else {
1254
1261
  // Single-tenant mode
1255
1262
  console.log('');
1256
- console.log(chalk.blue('━━━ Single-tenant URL Configuration ━━━'));
1263
+ console.log(chalk.blue('━━━ ' + t('tenant.singleTenantTitle') + ' ━━━'));
1257
1264
  console.log('');
1258
- console.log(chalk.gray(' In single-tenant mode:'));
1259
- console.log(chalk.gray(' • Issuer URL = API custom domain (or workers.dev fallback)'));
1260
- 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')));
1261
1268
  console.log('');
1262
1269
  tenantDisplayName = await input({
1263
- message: 'Organization name (display name)',
1270
+ message: t('tenant.organizationName'),
1264
1271
  default: 'Default Tenant',
1265
1272
  });
1266
1273
  const useCustomDomain = await confirm({
1267
- message: 'Configure custom domain?',
1274
+ message: t('domain.prompt'),
1268
1275
  default: false,
1269
1276
  });
1270
1277
  if (useCustomDomain) {
1271
1278
  console.log('');
1272
- console.log(chalk.gray(' Enter custom domains (leave empty to use Cloudflare defaults)'));
1279
+ console.log(chalk.gray(' ' + t('domain.enterDomains')));
1273
1280
  console.log('');
1274
1281
  apiDomain = await input({
1275
- message: 'API / Issuer domain (e.g., auth.example.com)',
1282
+ message: t('domain.apiDomain'),
1276
1283
  validate: (value) => {
1277
1284
  if (!value)
1278
1285
  return true;
1279
1286
  if (!/^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/.test(value)) {
1280
- return 'Please enter a valid domain';
1287
+ return t('domain.customValidation');
1281
1288
  }
1282
1289
  return true;
1283
1290
  },
1284
1291
  });
1285
1292
  loginUiDomain = await input({
1286
- message: 'Login UI domain (Enter to skip)',
1293
+ message: t('domain.loginUiDomain'),
1287
1294
  default: '',
1288
1295
  });
1289
1296
  adminUiDomain = await input({
1290
- message: 'Admin UI domain (Enter to skip)',
1297
+ message: t('domain.adminUiDomain'),
1291
1298
  default: '',
1292
1299
  });
1293
1300
  }
1294
1301
  if (apiDomain) {
1295
1302
  console.log('');
1296
- console.log(chalk.green(' ✓ Issuer URL: https://' + apiDomain));
1303
+ console.log(chalk.green(' ✓ ' + t('domain.issuerUrl', { url: 'https://' + apiDomain })));
1297
1304
  }
1298
1305
  else {
1299
1306
  console.log('');
1300
- console.log(chalk.green(' ✓ Issuer URL: ' + getWorkersDevUrl(envPrefix + '-ar-router')));
1301
- 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')));
1302
1309
  }
1303
1310
  }
1304
1311
  // Step 5: Optional components
1305
1312
  console.log('');
1306
- console.log(chalk.blue('━━━ Optional Components ━━━'));
1307
- 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')));
1308
1315
  console.log('');
1309
1316
  const enableSaml = await confirm({
1310
- message: 'Enable SAML support?',
1317
+ message: t('components.samlPrompt'),
1311
1318
  default: false,
1312
1319
  });
1313
1320
  const enableVc = await confirm({
1314
- message: 'Enable Verifiable Credentials?',
1321
+ message: t('components.vcPrompt'),
1315
1322
  default: false,
1316
1323
  });
1317
1324
  // Step 6: Feature flags
1318
1325
  console.log('');
1319
- console.log(chalk.blue('━━━ Feature Flags ━━━'));
1326
+ console.log(chalk.blue('━━━ ' + t('features.title') + ' ━━━'));
1320
1327
  console.log('');
1321
1328
  const enableQueue = await confirm({
1322
- message: 'Enable Cloudflare Queues? (for audit logs)',
1329
+ message: t('features.queuePrompt'),
1323
1330
  default: false,
1324
1331
  });
1325
1332
  const enableR2 = await confirm({
1326
- message: 'Enable Cloudflare R2? (for avatars)',
1333
+ message: t('features.r2Prompt'),
1327
1334
  default: false,
1328
1335
  });
1329
1336
  const emailProviderChoice = await select({
1330
- message: 'Select email provider',
1337
+ message: t('email.title'),
1331
1338
  choices: [
1332
- { value: 'none', name: 'None (configure later)' },
1333
- { value: 'resend', name: 'Resend' },
1339
+ { value: 'none', name: t('email.skipOption') },
1340
+ { value: 'resend', name: t('email.resendOption') },
1334
1341
  { value: 'sendgrid', name: 'SendGrid (coming soon)', disabled: true },
1335
- { value: 'ses', name: 'AWS SES (coming soon)', disabled: true },
1342
+ { value: 'ses', name: t('email.sesOption') + ' (coming soon)', disabled: true },
1336
1343
  ],
1337
1344
  default: 'none',
1338
1345
  });
@@ -1340,33 +1347,33 @@ async function runNormalSetup(options) {
1340
1347
  let emailConfigNormal = { provider: 'none' };
1341
1348
  if (emailProviderChoice === 'resend') {
1342
1349
  console.log('');
1343
- console.log(chalk.blue('━━━ Resend Configuration ━━━'));
1344
- console.log(chalk.gray('Get your API key at: https://resend.com/api-keys'));
1345
- 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')));
1346
1353
  console.log('');
1347
1354
  const resendApiKey = await password({
1348
- message: 'Resend API key',
1355
+ message: t('email.apiKeyPrompt'),
1349
1356
  mask: '*',
1350
1357
  validate: (value) => {
1351
1358
  if (!value.trim())
1352
- return 'API key is required';
1359
+ return t('email.apiKeyRequired');
1353
1360
  if (!value.startsWith('re_')) {
1354
- return 'Warning: Resend API keys typically start with "re_"';
1361
+ return t('email.apiKeyWarning');
1355
1362
  }
1356
1363
  return true;
1357
1364
  },
1358
1365
  });
1359
1366
  const fromAddress = await input({
1360
- message: 'From email address',
1367
+ message: t('email.fromAddressPrompt'),
1361
1368
  default: 'noreply@yourdomain.com',
1362
1369
  validate: (value) => {
1363
1370
  if (!value.includes('@'))
1364
- return 'Please enter a valid email address';
1371
+ return t('email.fromAddressValidation');
1365
1372
  return true;
1366
1373
  },
1367
1374
  });
1368
1375
  const fromName = await input({
1369
- message: 'From display name (optional)',
1376
+ message: t('email.fromNamePrompt'),
1370
1377
  default: 'Authrim',
1371
1378
  });
1372
1379
  emailConfigNormal = {
@@ -1376,12 +1383,12 @@ async function runNormalSetup(options) {
1376
1383
  apiKey: resendApiKey,
1377
1384
  };
1378
1385
  console.log('');
1379
- console.log(chalk.yellow('⚠️ Domain verification required for sending from your own domain.'));
1380
- 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')));
1381
1388
  }
1382
1389
  // Step 7: Advanced OIDC settings
1383
1390
  const configureOidc = await confirm({
1384
- message: 'Configure OIDC settings? (token TTL, etc.)',
1391
+ message: t('oidc.configurePrompt'),
1385
1392
  default: false,
1386
1393
  });
1387
1394
  let accessTokenTtl = 3600; // 1 hour
@@ -1390,76 +1397,76 @@ async function runNormalSetup(options) {
1390
1397
  let pkceRequired = true;
1391
1398
  if (configureOidc) {
1392
1399
  console.log('');
1393
- console.log(chalk.blue('━━━ OIDC Settings ━━━'));
1400
+ console.log(chalk.blue('━━━ ' + t('oidc.title') + ' ━━━'));
1394
1401
  console.log('');
1395
1402
  const accessTokenTtlStr = await input({
1396
- message: 'Access Token TTL (sec)',
1403
+ message: t('oidc.accessTokenTtl'),
1397
1404
  default: '3600',
1398
1405
  validate: (value) => {
1399
1406
  const num = parseInt(value, 10);
1400
1407
  if (isNaN(num) || num <= 0)
1401
- return 'Please enter a positive integer';
1408
+ return t('oidc.positiveInteger');
1402
1409
  return true;
1403
1410
  },
1404
1411
  });
1405
1412
  accessTokenTtl = parseInt(accessTokenTtlStr, 10);
1406
1413
  const refreshTokenTtlStr = await input({
1407
- message: 'Refresh Token TTL (sec)',
1414
+ message: t('oidc.refreshTokenTtl'),
1408
1415
  default: '604800',
1409
1416
  validate: (value) => {
1410
1417
  const num = parseInt(value, 10);
1411
1418
  if (isNaN(num) || num <= 0)
1412
- return 'Please enter a positive integer';
1419
+ return t('oidc.positiveInteger');
1413
1420
  return true;
1414
1421
  },
1415
1422
  });
1416
1423
  refreshTokenTtl = parseInt(refreshTokenTtlStr, 10);
1417
1424
  const authCodeTtlStr = await input({
1418
- message: 'Authorization Code TTL (sec)',
1425
+ message: t('oidc.authCodeTtl'),
1419
1426
  default: '600',
1420
1427
  validate: (value) => {
1421
1428
  const num = parseInt(value, 10);
1422
1429
  if (isNaN(num) || num <= 0)
1423
- return 'Please enter a positive integer';
1430
+ return t('oidc.positiveInteger');
1424
1431
  return true;
1425
1432
  },
1426
1433
  });
1427
1434
  authCodeTtl = parseInt(authCodeTtlStr, 10);
1428
1435
  pkceRequired = await confirm({
1429
- message: 'Require PKCE?',
1436
+ message: t('oidc.pkceRequired'),
1430
1437
  default: true,
1431
1438
  });
1432
1439
  }
1433
1440
  // Step 8: Sharding settings
1434
1441
  const configureSharding = await confirm({
1435
- message: 'Configure sharding? (for high-load environments)',
1442
+ message: t('sharding.configurePrompt'),
1436
1443
  default: false,
1437
1444
  });
1438
1445
  let authCodeShards = 64;
1439
1446
  let refreshTokenShards = 8;
1440
1447
  if (configureSharding) {
1441
1448
  console.log('');
1442
- console.log(chalk.blue('━━━ Sharding Settings ━━━'));
1443
- 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')));
1444
1451
  console.log('');
1445
1452
  const authCodeShardsStr = await input({
1446
- message: 'Auth Code shard count',
1453
+ message: t('sharding.authCodeShards'),
1447
1454
  default: '64',
1448
1455
  validate: (value) => {
1449
1456
  const num = parseInt(value, 10);
1450
1457
  if (isNaN(num) || num <= 0)
1451
- return 'Please enter a positive integer';
1458
+ return t('oidc.positiveInteger');
1452
1459
  return true;
1453
1460
  },
1454
1461
  });
1455
1462
  authCodeShards = parseInt(authCodeShardsStr, 10);
1456
1463
  const refreshTokenShardsStr = await input({
1457
- message: 'Refresh Token shard count',
1464
+ message: t('sharding.refreshTokenShards'),
1458
1465
  default: '8',
1459
1466
  validate: (value) => {
1460
1467
  const num = parseInt(value, 10);
1461
1468
  if (isNaN(num) || num <= 0)
1462
- return 'Please enter a positive integer';
1469
+ return t('oidc.positiveInteger');
1463
1470
  return true;
1464
1471
  },
1465
1472
  });
@@ -1467,32 +1474,36 @@ async function runNormalSetup(options) {
1467
1474
  }
1468
1475
  // Step 9: Database Configuration
1469
1476
  console.log('');
1470
- console.log(chalk.blue('━━━ Database Configuration ━━━'));
1471
- 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')));
1472
1479
  console.log('');
1473
1480
  const dbLocationChoices = [
1474
- { name: 'Automatic (nearest to you)', value: 'auto' },
1475
- { name: '── Location Hints ──', value: '__separator1__', disabled: true },
1476
- { name: 'North America (West)', value: 'wnam' },
1477
- { name: 'North America (East)', value: 'enam' },
1478
- { name: 'Europe (West)', value: 'weur' },
1479
- { name: 'Europe (East)', value: 'eeur' },
1480
- { name: 'Asia Pacific', value: 'apac' },
1481
- { name: 'Oceania', value: 'oc' },
1482
- { name: '── Jurisdiction (Compliance) ──', value: '__separator2__', disabled: true },
1483
- { 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' },
1484
1495
  ];
1485
- console.log(chalk.gray(' Core DB: Stores OAuth clients, tokens, sessions, audit logs'));
1496
+ console.log(chalk.gray(' ' + t('db.coreDescription')));
1486
1497
  const coreDbLocation = await select({
1487
- message: 'Core Database region',
1498
+ message: t('db.coreRegion'),
1488
1499
  choices: dbLocationChoices,
1489
1500
  default: 'auto',
1490
1501
  });
1491
1502
  console.log('');
1492
- console.log(chalk.gray(' PII DB: Stores user profiles, credentials, personal data'));
1493
- 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')));
1494
1505
  const piiDbLocation = await select({
1495
- message: 'PII Database region',
1506
+ message: t('db.piiRegion'),
1496
1507
  choices: dbLocationChoices,
1497
1508
  default: 'auto',
1498
1509
  });