Skip to content

Commit 40b8b80

Browse files
committed
WIP: move environment detection to config and start webpack work
1 parent 1adc476 commit 40b8b80

6 files changed

Lines changed: 96 additions & 54 deletions

File tree

cmp/compiler/src/metadata/manager.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,13 @@ export function cleanupExistingMetadata(metadataFilePath: string) {
4444
/**
4545
* Get the absolute path to the metadata file
4646
*
47-
* @param config - Config with sourceRoot and lingoDir
47+
* @param config - Config with sourceRoot, lingoDir, and environment
4848
* @returns Absolute path to metadata file
4949
*/
5050
export function getMetadataPath(config: PathConfig): string {
5151
const filename =
5252
// Similar to next keeping dev build separate, let's keep the build metadata clean of any dev mode additions
53-
process.env.NODE_ENV === "development"
53+
config.environment === "development"
5454
? "metadata-dev.json"
5555
: "metadata-build.json";
5656
return path.join(getLingoDir(config), filename);

cmp/compiler/src/plugin/next.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ export async function withLingo(
8282
): Promise<NextConfig> {
8383
const lingoConfig = createLingoConfig(lingoOptions);
8484
let metadataFilePath = getMetadataPath(lingoConfig);
85-
const isDev = process.env.NODE_ENV === "development";
85+
const isDev = lingoConfig.environment === "development";
8686

8787
logger.debug(
8888
`Initializing Lingo.dev compiler. Is dev mode: ${isDev}. Is main runner: ${isMainRunner()}`,

cmp/compiler/src/plugin/unplugin.ts

Lines changed: 67 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@ import {
1616
startTranslationServer,
1717
type TranslationServer,
1818
} from "../translation-server";
19-
import { getMetadataPath, MetadataManager } from "../metadata/manager";
19+
import {
20+
cleanupExistingMetadata,
21+
getMetadataPath,
22+
MetadataManager,
23+
} from "../metadata/manager";
2024
import { createLingoConfig } from "../utils/config-factory";
2125
import { logger } from "../utils/logger";
2226
import { getCacheDir } from "../utils/path-helpers";
@@ -36,60 +40,29 @@ let translationServer: TranslationServer;
3640

3741
const PLUGIN_NAME = "lingo-compiler";
3842

39-
export async function cleanup(
40-
server: TranslationServer | undefined,
41-
metadataFilePath: string,
42-
) {
43-
// General cleanup. Delete metadata and stop the server if any was started.
44-
logger.debug(`Attempting to cleanup metadata file: ${metadataFilePath}`);
45-
46-
try {
47-
fs.unlinkSync(metadataFilePath);
48-
logger.info(`🧹 Cleaned up build metadata file: ${metadataFilePath}`);
49-
} catch (error: any) {
50-
// Ignore if file doesn't exist
51-
if (error.code === "ENOENT") {
52-
logger.debug(
53-
`Metadata file already deleted or doesn't exist: ${metadataFilePath}`,
54-
);
55-
} else {
56-
logger.warn(`Failed to cleanup metadata file: ${error.message}`);
57-
}
58-
}
59-
60-
if (server) {
61-
logger.debug("Stopping translation server...");
62-
await server.stop();
63-
logger.info("Translation server stopped");
64-
}
65-
}
66-
6743
/**
6844
* Universal plugin for Lingo.dev compiler
6945
* Supports Vite, Webpack, Rollup, and esbuild
7046
*/
7147
export const lingoUnplugin = createUnplugin<LingoPluginOptions>((options) => {
7248
const config = createLingoConfig(options);
7349

74-
const isDev = process.env.NODE_ENV === "development";
50+
// Won't work for webpack most likely. Use mode there to set correct environment in configs.
51+
const isDev = config.environment === "development";
7552
const startPort = config.dev.translationServerStartPort;
7653
const metadataFilePath = getMetadataPath(config);
7754

78-
// Warn if NODE_ENV is not set (common webpack issue)
79-
if (!process.env.NODE_ENV) {
80-
logger.warn(
81-
"⚠️ process.env.NODE_ENV is undefined. Lingo will assume production mode.\n" +
82-
" For webpack users: Add webpack.DefinePlugin to set process.env.NODE_ENV.\n" +
83-
" See: https://webpack.js.org/plugins/define-plugin/",
84-
);
85-
}
86-
8755
return {
8856
name: PLUGIN_NAME,
8957
enforce: "pre", // Run before other plugins (especially before React plugin)
9058

9159
vite: {
9260
async buildStart() {
61+
cleanupExistingMetadata(metadataFilePath);
62+
63+
registerCleanupOnCurrentProcess({
64+
cleanup: () => cleanupExistingMetadata(metadataFilePath),
65+
});
9366
if (isDev && !translationServer) {
9467
translationServer = await startTranslationServer({
9568
startPort,
@@ -112,7 +85,6 @@ export const lingoUnplugin = createUnplugin<LingoPluginOptions>((options) => {
11285
}
11386
},
11487

115-
// Doesn't fire when error happens
11688
async buildEnd() {
11789
if (!isDev) {
11890
try {
@@ -123,15 +95,64 @@ export const lingoUnplugin = createUnplugin<LingoPluginOptions>((options) => {
12395
});
12496
} catch (error) {
12597
logger.error("Build-time translation processing failed:", error);
126-
// throw error;
12798
}
12899
}
129-
130-
logger.warn("Build end");
131-
await cleanup(translationServer, metadataFilePath);
132100
},
133101
},
134102

103+
webpack(compiler) {
104+
// if (config.isEmbeddedIntoNext) {
105+
// return;
106+
// }
107+
compiler.hooks.watchRun.tapPromise(PLUGIN_NAME, async () => {
108+
cleanupExistingMetadata(metadataFilePath);
109+
registerCleanupOnCurrentProcess({
110+
cleanup: () => cleanupExistingMetadata(metadataFilePath),
111+
});
112+
113+
if (compiler.options.mode === "development" && !translationServer) {
114+
translationServer = await startTranslationServer({
115+
startPort,
116+
onError: (err) => {
117+
logger.error("Translation server error:", err);
118+
},
119+
onReady: (port) => {
120+
logger.info(
121+
`Translation server started successfully on port: ${port}`,
122+
);
123+
},
124+
config: { ...config, environment: compiler.options.mode },
125+
});
126+
registerCleanupOnCurrentProcess({
127+
asyncCleanup: async () => {
128+
await translationServer.stop();
129+
},
130+
});
131+
}
132+
});
133+
134+
compiler.hooks.additionalPass.tapPromise(PLUGIN_NAME, async () => {
135+
if (compiler.options.mode === "production") {
136+
try {
137+
await processBuildTranslations({
138+
config: { ...config, environment: compiler.options.mode },
139+
publicOutputPath: "public/translations",
140+
metadataFilePath: metadataFilePath,
141+
});
142+
} catch (error) {
143+
logger.error("Build-time translation processing failed:", error);
144+
throw error;
145+
}
146+
}
147+
});
148+
149+
// Duplicates process handlers, but won't hurt
150+
compiler.hooks.shutdown.tapPromise(PLUGIN_NAME, async () => {
151+
cleanupExistingMetadata(metadataFilePath);
152+
await translationServer?.stop();
153+
});
154+
},
155+
135156
resolveId(id) {
136157
if (id === "@lingo.dev/compiler/dev-config") {
137158
// Return a virtual module ID (prefix with \0 to mark it as virtual)
@@ -169,6 +190,7 @@ export const lingoUnplugin = createUnplugin<LingoPluginOptions>((options) => {
169190
},
170191

171192
load(id) {
193+
logger.warn(`ID: ${id}`);
172194
if (id === "\0virtual:lingo-dev-config") {
173195
const serverUrl =
174196
translationServer?.getUrl() || `http://127.0.0.1:${startPort}`;
@@ -218,6 +240,7 @@ export function persistLocale(locale) {
218240
},
219241
handler: async (code, id) => {
220242
try {
243+
// TODO (AleksandrSl 13/12/2025): How do I get Webpack mode here?
221244
// Transform the component
222245
const result = transformComponent({
223246
code,
@@ -243,7 +266,6 @@ export function persistLocale(locale) {
243266

244267
await metadataManager.saveMetadataWithEntries(result.newEntries);
245268

246-
// Log new translations discovered (in dev mode)
247269
if (isDev) {
248270
logger.info(
249271
`Found ${result.newEntries.length} translatable text(s) in ${id}`,

cmp/compiler/src/translators/translator-factory.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export interface TranslatorFactoryConfig {
1515
sourceLocale: string;
1616
models?: "lingo.dev" | Record<string, string>;
1717
prompt?: string;
18+
environment: "development" | "production";
1819
dev?: {
1920
usePseudotranslator?: boolean;
2021
};
@@ -42,7 +43,7 @@ export function createTranslator(
4243
config: TranslatorFactoryConfig,
4344
logger: Logger,
4445
): Translator<any> {
45-
const isDev = process.env.NODE_ENV === "development";
46+
const isDev = config.environment === "development";
4647

4748
// 1. Explicit dev override takes precedence
4849
if (isDev && config.dev?.usePseudotranslator) {

cmp/compiler/src/types.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,17 @@ export type LocalePersistenceConfig = { type: "cookie"; cookieName?: string };
3232
*/
3333
export type LingoConfigRequiredFields = "sourceLocale" | "targetLocales";
3434

35+
export type LingoInternalFields = "environment";
36+
3537
/**
3638
* Configuration for the Lingo compiler
3739
*/
3840
export type PartialLingoConfig = Pick<LingoConfig, LingoConfigRequiredFields> &
3941
Partial<
40-
Omit<LingoConfig, LingoConfigRequiredFields | "dev"> & {
42+
Omit<
43+
LingoConfig,
44+
LingoConfigRequiredFields | "dev" | LingoInternalFields
45+
> & {
4146
dev: Partial<LingoConfig["dev"]>;
4247
}
4348
>;
@@ -74,6 +79,14 @@ export type LingoConfig = {
7479
*/
7580
lingoDir: string;
7681

82+
/**
83+
* Environment mode
84+
* Determines metadata file naming and translator behavior
85+
*
86+
* @default "production"
87+
*/
88+
environment: "development" | "production";
89+
7790
/**
7891
* The locale to translate from.
7992
*
@@ -216,13 +229,17 @@ export type TranslationMiddlewareConfig = Pick<
216229
| "targetLocales"
217230
| "dev"
218231
| "pluralization"
232+
| "environment"
219233
>;
220234

221235
/**
222236
* Config needed for path operations
223-
* Alias for MetadataConfig (same fields)
237+
* Includes environment to determine metadata file naming
224238
*/
225-
export type PathConfig = Pick<LingoConfig, "sourceRoot" | "lingoDir">;
239+
export type PathConfig = Pick<
240+
LingoConfig,
241+
"sourceRoot" | "lingoDir" | "environment"
242+
>;
226243

227244
export type MetadataTranslationEntry = BaseTranslationEntry<
228245
"metadata",

cmp/compiler/src/utils/config-factory.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export const DEFAULT_CONFIG = {
1616
useDirective: false,
1717
skipPatterns: [/node_modules/, /\.spec\./, /\.test\./] as RegExp[],
1818
dev: {
19-
serverStartPort: 60000,
19+
translationServerStartPort: 60000,
2020
},
2121
cookieConfig: {
2222
name: "locale",
@@ -32,7 +32,7 @@ export const DEFAULT_CONFIG = {
3232
model: "groq:llama-3.1-8b-instant",
3333
},
3434
buildMode: "translate",
35-
} satisfies Omit<LingoConfig, LingoConfigRequiredFields>;
35+
} satisfies Omit<LingoConfig, LingoConfigRequiredFields | "environment">;
3636

3737
/**
3838
* Create a LoaderConfig with defaults applied
@@ -45,6 +45,8 @@ export function createLingoConfig(options: PartialLingoConfig): LingoConfig {
4545
return {
4646
...DEFAULT_CONFIG,
4747
...options,
48+
environment:
49+
process.env.NODE_ENV === "development" ? "development" : "production",
4850
dev: {
4951
...DEFAULT_CONFIG.dev,
5052
...options.dev,

0 commit comments

Comments
 (0)