Skip to content

Commit 1b6f5b6

Browse files
committed
fix minor styling, allow custom translations and prepare for release
1 parent 1f2379d commit 1b6f5b6

10 files changed

Lines changed: 541 additions & 61 deletions

File tree

app/_consts/global.ts

Lines changed: 0 additions & 4 deletions
This file was deleted.
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import fs from "fs";
2+
import path from "path";
3+
import "server-only";
4+
5+
/**
6+
* Load translation messages for a given locale.
7+
* First checks for custom translations in ./data/translations/,
8+
* then falls back to built-in translations in app/_translations/.
9+
*
10+
* This function is server-only and should only be called from server components
11+
* or server actions.
12+
*/
13+
export const loadTranslationMessages = async (locale: string): Promise<any> => {
14+
const customTranslationPath = path.join(
15+
process.cwd(),
16+
"data",
17+
"translations",
18+
`${locale}.json`
19+
);
20+
21+
try {
22+
if (fs.existsSync(customTranslationPath)) {
23+
const customMessages = JSON.parse(
24+
fs.readFileSync(customTranslationPath, "utf8")
25+
);
26+
return customMessages;
27+
}
28+
} catch (error) {
29+
console.warn(`Failed to load custom translation for ${locale}:`, error);
30+
}
31+
32+
try {
33+
const messages = (await import(`../../../_translations/${locale}.json`))
34+
.default;
35+
return messages;
36+
} catch (error) {
37+
const fallbackMessages = (await import("../../../_translations/en.json"))
38+
.default;
39+
return fallbackMessages;
40+
}
41+
};
42+
43+
type TranslationFunction = (key: string) => string;
44+
45+
/**
46+
* Get a translation function for a given locale.
47+
* This function is server-only and should only be called from server components
48+
* or server actions.
49+
*/
50+
export const getTranslations = async (
51+
locale: string = process.env.LOCALE || "en"
52+
): Promise<TranslationFunction> => {
53+
const messages = await loadTranslationMessages(locale);
54+
55+
return (key: string) => {
56+
const keys = key.split(".");
57+
let value: any = messages;
58+
for (const k of keys) {
59+
value = value?.[k];
60+
}
61+
return value || key;
62+
};
63+
};

app/_utils/global-utils.ts

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,6 @@ export const cn = (...inputs: ClassValue[]) => {
55
return twMerge(clsx(inputs));
66
};
77

8-
type TranslationFunction = (key: string) => string;
9-
10-
export const getTranslations = async (
11-
locale: string = process.env.LOCALE || "en"
12-
): Promise<TranslationFunction> => {
13-
const messages = (await import(`../_translations/${locale}.json`)).default;
14-
15-
return (key: string) => {
16-
const keys = key.split(".");
17-
let value: any = messages;
18-
for (const k of keys) {
19-
value = value?.[k];
20-
}
21-
return value || key;
22-
};
23-
};
24-
258
export const copyToClipboard = async (text: string): Promise<boolean> => {
269
try {
2710
if (navigator?.clipboard?.writeText) {

app/api/system-stats/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { NextRequest, NextResponse } from "next/server";
2-
import { getTranslations } from "@/app/_utils/global-utils";
2+
import { getTranslations } from "@/app/_server/actions/translations";
33
import * as si from "systeminformation";
44
import {
55
getPing,

app/i18n.ts

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
import { getRequestConfig } from "next-intl/server";
2-
import { Locales } from "@/app/_consts/global";
3-
4-
const validLocales = Locales.map((item) => item.locale);
2+
import { loadTranslationMessages } from "@/app/_server/actions/translations";
53

64
export default getRequestConfig(async ({ locale }) => {
7-
const safeLocale = locale && validLocales.includes(locale) ? locale : "en";
5+
const safeLocale = locale || "en";
86

9-
try {
10-
return {
11-
locale: safeLocale,
12-
messages: (await import(`./_translations/${safeLocale}.json`)).default,
13-
};
14-
} catch (error) {
15-
console.error(
16-
`Failed to load translations for locale: ${safeLocale}`,
17-
error
18-
);
19-
return {
20-
locale: "en",
21-
messages: (await import("./_translations/en.json")).default,
22-
};
23-
}
24-
});
7+
try {
8+
const messages = await loadTranslationMessages(safeLocale);
9+
return {
10+
locale: safeLocale,
11+
messages,
12+
};
13+
} catch (error) {
14+
console.error(
15+
`Failed to load translations for locale: ${safeLocale}`,
16+
error
17+
);
18+
const fallbackMessages = await loadTranslationMessages("en");
19+
return {
20+
locale: "en",
21+
messages: fallbackMessages,
22+
};
23+
}
24+
});

app/layout.tsx

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { JetBrains_Mono, Inter } from "next/font/google";
33
import "@/app/globals.css";
44
import { ThemeProvider } from "@/app/_providers/ThemeProvider";
55
import { ServiceWorkerRegister } from "@/app/_components/FeatureComponents/PWA/ServiceWorkerRegister";
6-
import { Locales } from "@/app/_consts/global";
6+
import { loadTranslationMessages } from "@/app/_server/actions/translations";
77

88
import { NextIntlClientProvider } from "next-intl";
99

@@ -21,7 +21,8 @@ const inter = Inter({
2121

2222
export const metadata: Metadata = {
2323
title: "Cr*nMaster - Cron Management made easy",
24-
description: "The ultimate cron job management platform with intelligent scheduling, real-time monitoring, and powerful automation tools",
24+
description:
25+
"The ultimate cron job management platform with intelligent scheduling, real-time monitoring, and powerful automation tools",
2526
manifest: "/manifest.json",
2627
appleWebApp: {
2728
capable: true,
@@ -54,12 +55,7 @@ export default async function RootLayout({
5455
let locale = process.env.LOCALE || "en";
5556
let messages;
5657

57-
58-
if (!Locales.some((item) => item.locale === locale)) {
59-
locale = "en";
60-
}
61-
62-
messages = (await import(`./_translations/${locale}.json`)).default;
58+
messages = await loadTranslationMessages(locale);
6359

6460
return (
6561
<html lang="en" suppressHydrationWarning>
@@ -72,7 +68,6 @@ export default async function RootLayout({
7268
<link rel="apple-touch-icon" href="/logo.png" />
7369
</head>
7470
<body className={`${inter.variable} ${jetbrainsMono.variable} font-sans`}>
75-
7671
<NextIntlClientProvider locale={locale} messages={messages}>
7772
<ThemeProvider
7873
attribute="class"

app/page.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,18 @@ import { ThemeToggle } from "@/app/_components/FeatureComponents/Theme/ThemeTogg
66
import { LogoutButton } from "@/app/_components/FeatureComponents/LoginForm/LogoutButton";
77
import { ToastContainer } from "@/app/_components/GlobalComponents/UIElements/Toast";
88
import { PWAInstallPrompt } from "@/app/_components/FeatureComponents/PWA/PWAInstallPrompt";
9-
import { getTranslations } from "@/app/_utils/global-utils";
9+
import { getTranslations } from "@/app/_server/actions/translations";
1010
import { SSEProvider } from "@/app/_contexts/SSEContext";
1111

1212
export const dynamic = "force-dynamic";
1313
export const maxDuration = 300;
1414

1515
export default async function Home() {
1616
const t = await getTranslations();
17-
const liveUpdatesEnabled = (typeof process.env.LIVE_UPDATES === "boolean" && process.env.LIVE_UPDATES === true) || process.env.LIVE_UPDATES !== "false";
17+
const liveUpdatesEnabled =
18+
(typeof process.env.LIVE_UPDATES === "boolean" &&
19+
process.env.LIVE_UPDATES === true) ||
20+
process.env.LIVE_UPDATES !== "false";
1821

1922
const [cronJobs, scripts] = await Promise.all([
2023
getCronJobs(),

howto/DOCKER.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ environment:
5252
#### Localization
5353

5454
```yaml
55-
- LOCALE=en # or other supported locales (see /app/_translations/)
55+
- LOCALE=en # or any locale code (supports custom translations in ./data/translations/)
5656
```
5757

5858
#### Logging Configuration

howto/ENV_VARIABLES.md

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,35 @@ This document provides a comprehensive reference for all environment variables u
1515
| Variable | Default | Description |
1616
| --------------- | ------------- | ---------------------------------------------------------------------------- |
1717
| `APP_URL` | Auto-detected | Public URL of your Cronmaster instance (e.g., `https://cron.yourdomain.com`) |
18-
| `LOCALE` | `en` | Application locale/language setting |
18+
| `LOCALE` | `en` | Application locale/language setting (supports custom translations) |
1919
| `HOME` | `/home` | Path to home directory (optional override) |
2020
| `AUTH_PASSWORD` | `N/A` | Password for authentication (can be used alone or with SSO) |
2121

22+
## Custom Translations
23+
24+
CronMaster supports custom user-made translations. You can create your own translation files and use them by setting the `LOCALE` environment variable.
25+
26+
**For detailed instructions on creating custom translations or contributing official translations, see [TRANSLATIONS.md](TRANSLATIONS.md).**
27+
28+
### Quick Setup for Custom Translations
29+
30+
```bash
31+
# Create translations directory
32+
mkdir -p ./data/translations
33+
34+
# Copy template and customize
35+
cp app/_translations/en.json ./data/translations/your-locale.json
36+
37+
# Set locale and restart
38+
export LOCALE=your-locale
39+
```
40+
41+
Translation loading priority:
42+
43+
1. Custom: `./data/translations/{locale}.json`
44+
2. Built-in: `app/_translations/{locale}.json`
45+
3. Fallback: `app/_translations/en.json`
46+
2247
## Docker Configuration
2348

2449
| Variable | Default | Description |
@@ -34,11 +59,11 @@ This document provides a comprehensive reference for all environment variables u
3459

3560
## Logging Configuration
3661

37-
| Variable | Default | Description |
38-
| ------------------------------- | ------- | -------------------------------------------------------------------- |
39-
| `MAX_LOG_AGE_DAYS` | `30` | Days to keep job execution logs before cleanup |
40-
| `NEXT_PUBLIC_MAX_LOG_AGE_DAYS` | `30` | Days to keep error history in browser localStorage (client-side) |
41-
| `MAX_LOGS_PER_JOB` | `50` | Maximum number of log files to keep per job |
62+
| Variable | Default | Description |
63+
| ------------------------------ | ------- | ---------------------------------------------------------------- |
64+
| `MAX_LOG_AGE_DAYS` | `30` | Days to keep job execution logs before cleanup |
65+
| `NEXT_PUBLIC_MAX_LOG_AGE_DAYS` | `30` | Days to keep error history in browser localStorage (client-side) |
66+
| `MAX_LOGS_PER_JOB` | `50` | Maximum number of log files to keep per job |
4267

4368
## Authentication & Security
4469

@@ -109,7 +134,7 @@ services:
109134
- AUTH_PASSWORD=your_secure_password
110135
- HOST_CRONTAB_USER=root
111136
- APP_URL=https://cron.yourdomain.com
112-
- LOCALE=en
137+
- LOCALE=en # Can be any locale code, including custom ones
113138
- NEXT_PUBLIC_CLOCK_UPDATE_INTERVAL=30000
114139
- LIVE_UPDATES=true
115140
- MAX_LOG_AGE_DAYS=30

0 commit comments

Comments
 (0)