3838 */
3939
4040import { parseArgs } from "node:util" ;
41- import { readFile , access } from "fs/promises" ;
42- import { resolve , join } from "path" ;
41+ import { access } from "fs/promises" ;
42+ import { join } from "path" ;
4343import { pathToFileURL } from "url" ;
44- import type { TranslationMiddlewareConfig } from "../types" ;
44+ import type {
45+ LingoConfig ,
46+ PartialLingoConfig ,
47+ TranslationMiddlewareConfig ,
48+ } from "../types" ;
4549import type { LingoNextPluginOptions } from "../plugin/next" ;
4650import { logger } from "../utils/logger" ;
4751import { startOrGetTranslationServer } from "./translation-server" ;
52+ import { createLingoConfig } from "../utils/config-factory" ;
53+ import { type LocaleCode } from "lingo.dev/spec" ;
4854
4955interface CLIOptions {
5056 port ? : number ;
51- sourceLocale ? : string ;
52- targetLocales ?: string [ ] ;
57+ sourceLocale ? : LocaleCode ;
58+ targetLocales ?: LocaleCode [ ] ;
5359 lingoDir ?: string ;
5460 sourceRoot ? : string ;
55- models ? : string | Record < string , string > ;
61+ models ? : "lingo.dev" | Record < string , string > ;
5662 prompt ? : string ;
5763 timeout ? : number ;
5864 usePseudo ? : boolean ;
@@ -83,8 +89,11 @@ function parseCliArgs(): CLIOptions {
8389
8490 return {
8591 port : values . port ? parseInt ( values . port , 10 ) : undefined ,
86- sourceLocale : values [ "source-locale" ] ,
87- targetLocales : values [ "target-locales" ] ?. split ( "," ) . map ( ( s ) => s . trim ( ) ) ,
92+ // TODO (AleksandrSl 04/12/2025): Validation for LocaleCode is needed
93+ sourceLocale : values [ "source-locale" ] as LocaleCode ,
94+ targetLocales : values [ "target-locales" ]
95+ ?. split ( "," )
96+ . map ( ( s ) => s . trim ( ) ) as LocaleCode [ ] ,
8897 lingoDir : values [ "lingo-dir" ] ,
8998 sourceRoot : values [ "source-root" ] ,
9099 models : values . models ? parseModelsString ( values . models ) : undefined ,
@@ -230,7 +239,7 @@ function extractViteLingoConfig(
230239 */
231240async function loadBundlerConfig (
232241 cwd : string = process . cwd ( ) ,
233- ) : Promise < Partial < TranslationMiddlewareConfig > | null > {
242+ ) : Promise < PartialLingoConfig | null > {
234243 const detected = await findBundlerConfig ( cwd ) ;
235244
236245 if ( ! detected ) {
@@ -257,13 +266,13 @@ async function loadBundlerConfig(
257266 const lingoConfig = extractNextLingoConfig ( resolvedConfig ) ;
258267 if ( lingoConfig ) {
259268 logger . info ( "Extracted Lingo configuration from Next.js config" ) ;
260- return lingoConfig as Partial < TranslationMiddlewareConfig > ;
269+ return lingoConfig as PartialLingoConfig ;
261270 }
262271 } else if ( detected . type === "vite" ) {
263272 const lingoConfig = extractViteLingoConfig ( resolvedConfig ) ;
264273 if ( lingoConfig ) {
265274 logger . info ( "Extracted Lingo configuration from Vite config" ) ;
266- return lingoConfig ;
275+ return lingoConfig as PartialLingoConfig ;
267276 }
268277 }
269278
@@ -280,49 +289,34 @@ async function loadBundlerConfig(
280289 }
281290}
282291
283- /**
284- * Load configuration from JSON file (legacy support)
285- */
286- async function loadConfigFile (
287- configPath : string ,
288- ) : Promise < Partial < TranslationMiddlewareConfig >> {
289- try {
290- const absolutePath = resolve ( process . cwd ( ) , configPath ) ;
291- const content = await readFile ( absolutePath , "utf-8" ) ;
292- return JSON . parse ( content ) ;
293- } catch ( error ) {
294- logger . error ( `Failed to load config from ${ configPath } :` , error ) ;
295- throw error ;
296- }
297- }
298-
299- // TODO (AleksandrSl 04/12/2025): SHould probably use createloaderConfig?
300292/**
301293 * Merge CLI options with config file and defaults
302294 */
303295function buildConfig (
304296 cliOpts : CLIOptions ,
305- fileConfig ?: Partial < TranslationMiddlewareConfig > ,
306- ) : TranslationMiddlewareConfig {
297+ fileConfig ?: PartialLingoConfig ,
298+ ) : LingoConfig {
299+ const sourceLocale = cliOpts . sourceLocale || fileConfig ?. sourceLocale ;
300+ const targetLocales = cliOpts . targetLocales || fileConfig ?. targetLocales ;
301+ if ( ! sourceLocale || ! targetLocales ) {
302+ throw new Error (
303+ `Missing required sourceLocale or targetLocales. Please provide via CLI options or ensure your config uses the Lingo plugin` ,
304+ ) ;
305+ }
307306 // Priority: CLI > config file > defaults
308- const config : TranslationMiddlewareConfig = {
309- sourceLocale : ( cliOpts . sourceLocale ||
310- fileConfig ?. sourceLocale ||
311- "en" ) as any ,
312- targetLocales : ( cliOpts . targetLocales ||
313- fileConfig ?. targetLocales || [ "es" , "fr" , "de" ] ) as any ,
314- lingoDir : cliOpts . lingoDir || fileConfig ?. lingoDir || ".lingo" ,
307+ return createLingoConfig ( {
308+ sourceLocale,
309+ targetLocales,
310+ lingoDir : cliOpts . lingoDir || fileConfig ?. lingoDir ,
315311 sourceRoot : cliOpts . sourceRoot || fileConfig ?. sourceRoot || process . cwd ( ) ,
316- models : ( cliOpts . models || fileConfig ?. models || "lingo.dev" ) as any ,
312+ models : cliOpts . models || fileConfig ?. models ,
317313 prompt : cliOpts . prompt || fileConfig ?. prompt ,
318314 dev : {
319315 usePseudotranslator :
320- cliOpts . usePseudo ?? fileConfig ?. dev ?. usePseudotranslator ?? false ,
321- serverStartPort : fileConfig ?. dev ?. serverStartPort || 6000 ,
316+ cliOpts . usePseudo ?? fileConfig ?. dev ?. usePseudotranslator ,
317+ serverStartPort : fileConfig ?. dev ?. serverStartPort ,
322318 } ,
323- } ;
324-
325- return config ;
319+ } ) ;
326320}
327321
328322/**
@@ -416,22 +410,13 @@ export async function main(): Promise<void> {
416410 process . exit ( 0 ) ;
417411 }
418412
419- // Load configuration in priority order:
420- // 1. Explicit --config file (JSON)
421- // 2. Auto-detect bundler config (next.config.ts / vite.config.ts)
422- let fileConfig : Partial < TranslationMiddlewareConfig > | undefined ;
413+ // Try to auto-detect bundler config
414+ logger . info ( "Searching for bundler configuration..." ) ;
415+ const fileConfig : PartialLingoConfig | undefined =
416+ ( await loadBundlerConfig ( ) ) | | undefined ;
423417
424- if ( cliOpts . config ) {
425- logger . info ( `Loading configuration from ${ cliOpts . config } ...` ) ;
426- fileConfig = await loadConfigFile ( cliOpts . config ) ;
427- } else {
428- // Try to auto-detect bundler config
429- logger . info ( "Searching for bundler configuration..." ) ;
430- fileConfig = ( await loadBundlerConfig ( ) ) || undefined ;
431-
432- if ( ! fileConfig ) {
433- logger . info ( "No bundler config found, using defaults + CLI options" ) ;
434- }
418+ if ( ! fileConfig ) {
419+ logger . info ( "No bundler config found, using defaults + CLI options" ) ;
435420 }
436421
437422 // Build final configuration
0 commit comments