chore: bump version to 2.23.13 and update dependencies (#450)
* chore: bump version to 2.23.13 and update dependencies - Updated application version from 2.23.12 to 2.23.13 in package.json. - Upgraded several dependencies including @marsidev/react-turnstile to 1.4.2, @next/bundle-analyzer to 16.1.6, @next/eslint-plugin-next to 16.1.6, and others for improved functionality and security. - Adjusted package versions in pnpm-lock.yaml and pnpm-workspace.yaml for consistency across the project. - Removed unused AI translation functionality from translations-comparison component to streamline the codebase. * refactor: clean up code formatting and update Stripe API version - Removed unnecessary blank lines in LineItemDetails component for improved readability. - Enhanced formatting in PricingItem component for better clarity. - Updated Stripe API version from '2025-12-15.clover' to '2026-01-28.clover' for compatibility with the latest features. - Adjusted i18n initialization in email templates for consistency. - Reformatted props in AdminReactivateUserDialog for better structure. - Cleaned up type imports in ImageUploadInput component.
This commit is contained in:
committed by
GitHub
parent
58f08c5f39
commit
68276fda8a
@@ -1,8 +1,8 @@
|
||||
'use client';
|
||||
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { ChevronDownIcon, Loader2Icon } from 'lucide-react';
|
||||
import { ChevronDownIcon } from 'lucide-react';
|
||||
import { Subject, debounceTime } from 'rxjs';
|
||||
|
||||
import { Button } from '@kit/ui/button';
|
||||
@@ -32,10 +32,7 @@ import {
|
||||
} from '@kit/ui/table';
|
||||
import { cn } from '@kit/ui/utils';
|
||||
|
||||
import {
|
||||
translateWithAIAction,
|
||||
updateTranslationAction,
|
||||
} from '../lib/server-actions';
|
||||
import { updateTranslationAction } from '../lib/server-actions';
|
||||
import type { TranslationData, Translations } from '../lib/translations-loader';
|
||||
|
||||
function flattenTranslations(
|
||||
@@ -64,7 +61,6 @@ export function TranslationsComparison({
|
||||
translations: Translations;
|
||||
}) {
|
||||
const [search, setSearch] = useState('');
|
||||
const [isTranslating, setIsTranslating] = useState(false);
|
||||
|
||||
// Create RxJS Subject for handling translation updates
|
||||
const subject$ = useMemo(
|
||||
@@ -132,60 +128,6 @@ export function TranslationsComparison({
|
||||
setSelectedLocales(newSelectedLocales);
|
||||
};
|
||||
|
||||
const handleTranslateWithAI = useCallback(async () => {
|
||||
try {
|
||||
setIsTranslating(true);
|
||||
|
||||
// Get missing translations for the selected namespace
|
||||
const missingTranslations: Record<string, string> = {};
|
||||
const baseTranslations = flattenedTranslations[baseLocale] ?? {};
|
||||
|
||||
for (const locale of visibleLocales) {
|
||||
if (locale === baseLocale) continue;
|
||||
|
||||
const localeTranslations = flattenedTranslations[locale] ?? {};
|
||||
|
||||
for (const [key, value] of Object.entries(baseTranslations)) {
|
||||
if (!localeTranslations[key]) {
|
||||
missingTranslations[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(missingTranslations).length > 0) {
|
||||
await translateWithAIAction({
|
||||
sourceLocale: baseLocale,
|
||||
targetLocale: locale,
|
||||
namespace: selectedNamespace,
|
||||
translations: missingTranslations,
|
||||
});
|
||||
|
||||
toast.success(`Translated missing strings to ${locale}`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error('Failed to translate: ' + (error as Error).message);
|
||||
} finally {
|
||||
setIsTranslating(false);
|
||||
}
|
||||
}, [flattenedTranslations, baseLocale, visibleLocales, selectedNamespace]);
|
||||
|
||||
// Calculate if there are any missing translations
|
||||
const hasMissingTranslations = useMemo(() => {
|
||||
if (!flattenedTranslations || !baseLocale || !visibleLocales) return false;
|
||||
|
||||
const baseTranslations = flattenedTranslations[baseLocale] ?? {};
|
||||
|
||||
return visibleLocales.some((locale) => {
|
||||
if (locale === baseLocale) return false;
|
||||
|
||||
const localeTranslations = flattenedTranslations[locale] ?? {};
|
||||
|
||||
return Object.keys(baseTranslations).some(
|
||||
(key) => !localeTranslations[key],
|
||||
);
|
||||
});
|
||||
}, [flattenedTranslations, baseLocale, visibleLocales]);
|
||||
|
||||
// Set up subscription to handle debounced updates
|
||||
useEffect(() => {
|
||||
const subscription = subject$.pipe(debounceTime(500)).subscribe((props) => {
|
||||
@@ -262,22 +204,6 @@ export function TranslationsComparison({
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Button
|
||||
onClick={handleTranslateWithAI}
|
||||
disabled={isTranslating || !hasMissingTranslations}
|
||||
>
|
||||
{isTranslating ? (
|
||||
<>
|
||||
<Loader2Icon className="mr-2 h-4 w-4 animate-spin" />
|
||||
Translating...
|
||||
</>
|
||||
) : (
|
||||
'Translate missing with AI'
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -2,14 +2,10 @@
|
||||
|
||||
import { revalidatePath } from 'next/cache';
|
||||
|
||||
import { openai } from '@ai-sdk/openai';
|
||||
import { generateText } from 'ai';
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
||||
import { readFileSync, writeFileSync } from 'node:fs';
|
||||
import { resolve } from 'node:url';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { getLogger } from '@kit/shared/logger';
|
||||
|
||||
const Schema = z.object({
|
||||
locale: z.string().min(1),
|
||||
namespace: z.string().min(1),
|
||||
@@ -17,13 +13,6 @@ const Schema = z.object({
|
||||
value: z.string(),
|
||||
});
|
||||
|
||||
const TranslateSchema = z.object({
|
||||
sourceLocale: z.string(),
|
||||
targetLocale: z.string(),
|
||||
namespace: z.string(),
|
||||
translations: z.record(z.string(), z.string()),
|
||||
});
|
||||
|
||||
/**
|
||||
* Update a translation value in the specified locale and namespace.
|
||||
* @param props
|
||||
@@ -70,92 +59,3 @@ export async function updateTranslationAction(props: z.infer<typeof Schema>) {
|
||||
throw new Error('Failed to update translation');
|
||||
}
|
||||
}
|
||||
|
||||
export const translateWithAIAction = async (
|
||||
data: z.infer<typeof TranslateSchema>,
|
||||
) => {
|
||||
const logger = await getLogger();
|
||||
|
||||
z.string().min(1).parse(process.env.OPENAI_API_KEY);
|
||||
|
||||
try {
|
||||
const { sourceLocale, targetLocale, namespace, translations } =
|
||||
TranslateSchema.parse(data);
|
||||
|
||||
// if the path does not exist, create it using an empty object
|
||||
const root = resolve(process.cwd(), '..');
|
||||
const folderPath = `${root}apps/web/public/locales/${targetLocale}`;
|
||||
|
||||
if (!existsSync(folderPath)) {
|
||||
// create the directory if it doesn't exist
|
||||
mkdirSync(folderPath, { recursive: true });
|
||||
}
|
||||
|
||||
const filePath = `${folderPath}/${namespace}.json`;
|
||||
|
||||
if (!existsSync(filePath)) {
|
||||
// create the file if it doesn't exist
|
||||
writeFileSync(filePath, JSON.stringify({}, null, 2), 'utf8');
|
||||
}
|
||||
|
||||
const results: Record<string, string> = {};
|
||||
|
||||
// Process translations in batches of 5 for efficiency
|
||||
const entries = Object.entries(translations);
|
||||
const batches = [];
|
||||
|
||||
for (let i = 0; i < entries.length; i += 5) {
|
||||
batches.push(entries.slice(i, i + 5));
|
||||
}
|
||||
|
||||
for (const batch of batches) {
|
||||
const batchPromises = batch.map(async ([key, value]) => {
|
||||
const prompt = `Translate the following text from ${sourceLocale} to ${targetLocale}. Maintain any placeholders (like {name} or %{count}) and HTML tags. Only return the translated text, nothing else.
|
||||
|
||||
Original text: ${value}`;
|
||||
|
||||
const MODEL_NAME = process.env.LLM_MODEL_NAME ?? 'gpt-4o-mini';
|
||||
const model = openai(MODEL_NAME);
|
||||
|
||||
const { text } = await generateText({
|
||||
model,
|
||||
prompt,
|
||||
temperature: 0.3,
|
||||
maxTokens: 200,
|
||||
});
|
||||
|
||||
return [key, text.trim()] as [string, string];
|
||||
});
|
||||
|
||||
const batchResults = await Promise.all(batchPromises);
|
||||
|
||||
for (const [key, translation] of batchResults) {
|
||||
results[key] = translation;
|
||||
}
|
||||
}
|
||||
|
||||
// Update each translation
|
||||
for (const [key, translation] of Object.entries(results)) {
|
||||
await updateTranslationAction({
|
||||
locale: targetLocale,
|
||||
namespace,
|
||||
key,
|
||||
value: translation,
|
||||
});
|
||||
}
|
||||
|
||||
logger.info('AI translation completed', {
|
||||
sourceLocale,
|
||||
targetLocale,
|
||||
namespace,
|
||||
count: Object.keys(results).length,
|
||||
});
|
||||
|
||||
revalidatePath('/translations');
|
||||
|
||||
return { success: true, translations: results };
|
||||
} catch (error) {
|
||||
logger.error('AI translation failed', { error });
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -8,11 +8,9 @@
|
||||
"format": "prettier --check --write \"**/*.{ts,tsx}\" --ignore-path=\"../../.prettierignore\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/openai": "^2.0.88",
|
||||
"@faker-js/faker": "^10.2.0",
|
||||
"@hookform/resolvers": "^5.2.2",
|
||||
"@tanstack/react-query": "catalog:",
|
||||
"ai": "5.0.116",
|
||||
"lucide-react": "catalog:",
|
||||
"next": "catalog:",
|
||||
"nodemailer": "catalog:",
|
||||
|
||||
@@ -11,10 +11,10 @@
|
||||
},
|
||||
"author": "Makerkit",
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.58.0",
|
||||
"@playwright/test": "^1.58.1",
|
||||
"@supabase/supabase-js": "catalog:",
|
||||
"@types/node": "catalog:",
|
||||
"dotenv": "17.2.3",
|
||||
"dotenv": "17.2.4",
|
||||
"node-html-parser": "^7.0.2",
|
||||
"totp-generator": "^2.0.1"
|
||||
}
|
||||
|
||||
@@ -152,14 +152,12 @@ test.describe('Admin', () => {
|
||||
),
|
||||
]);
|
||||
|
||||
// TODO: find out why we need to reload the page only in CI
|
||||
await page.reload();
|
||||
|
||||
// Verify ban badge is removed
|
||||
await expect(page.getByText('Banned')).not.toBeVisible();
|
||||
|
||||
// Log out
|
||||
await page.context().clearCookies();
|
||||
await page.reload();
|
||||
|
||||
// Verify user can log in again
|
||||
await page.goto('/auth/sign-in');
|
||||
|
||||
@@ -53,8 +53,8 @@
|
||||
"@kit/ui": "workspace:*",
|
||||
"@makerkit/data-loader-supabase-core": "^0.0.10",
|
||||
"@makerkit/data-loader-supabase-nextjs": "^1.2.5",
|
||||
"@marsidev/react-turnstile": "^1.4.1",
|
||||
"@nosecone/next": "1.0.0",
|
||||
"@marsidev/react-turnstile": "^1.4.2",
|
||||
"@nosecone/next": "1.1.0",
|
||||
"@radix-ui/react-icons": "^1.3.2",
|
||||
"@supabase/supabase-js": "catalog:",
|
||||
"@tanstack/react-query": "catalog:",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "next-supabase-saas-kit-turbo",
|
||||
"version": "2.23.12",
|
||||
"version": "2.23.13",
|
||||
"private": true,
|
||||
"sideEffects": false,
|
||||
"engines": {
|
||||
|
||||
@@ -183,7 +183,6 @@ export function LineItemDetails(
|
||||
</If>
|
||||
</span>
|
||||
|
||||
|
||||
<If condition={!item.tiers?.length}>
|
||||
<span>-</span>
|
||||
|
||||
|
||||
@@ -162,10 +162,12 @@ function PricingItem(
|
||||
const i18nKey = `billing:units.${lineItem.unit}`;
|
||||
|
||||
const unitLabel = lineItem?.unit
|
||||
? i18n.exists(i18nKey) ? t(i18nKey, {
|
||||
count: 1,
|
||||
defaultValue: lineItem.unit,
|
||||
}) : lineItem.unit
|
||||
? i18n.exists(i18nKey)
|
||||
? t(i18nKey, {
|
||||
count: 1,
|
||||
defaultValue: lineItem.unit,
|
||||
})
|
||||
: lineItem.unit
|
||||
: '';
|
||||
|
||||
const isDefaultSeatUnit = lineItem?.unit === 'member';
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'server-only';
|
||||
|
||||
import { StripeServerEnvSchema } from '../schema/stripe-server-env.schema';
|
||||
|
||||
const STRIPE_API_VERSION = '2025-12-15.clover';
|
||||
const STRIPE_API_VERSION = '2026-01-28.clover';
|
||||
|
||||
/**
|
||||
* @description returns a Stripe instance
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { initializeServerI18n } from '@kit/i18n/server';
|
||||
import { createI18nSettings } from '@kit/i18n';
|
||||
import { initializeServerI18n } from '@kit/i18n/server';
|
||||
|
||||
export function initializeEmailI18n(params: {
|
||||
language: string | undefined;
|
||||
namespace: string;
|
||||
}) {
|
||||
const language = params.language ?? process.env.NEXT_PUBLIC_DEFAULT_LOCALE ?? 'en';
|
||||
const language =
|
||||
params.language ?? process.env.NEXT_PUBLIC_DEFAULT_LOCALE ?? 'en';
|
||||
|
||||
return initializeServerI18n(
|
||||
createI18nSettings({
|
||||
|
||||
@@ -52,7 +52,10 @@ export function AdminReactivateUserDialog(
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
|
||||
<ReactivateUserForm userId={props.userId} onSuccess={() => setOpen(false)} />
|
||||
<ReactivateUserForm
|
||||
userId={props.userId}
|
||||
onSuccess={() => setOpen(false)}
|
||||
/>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
);
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"devDependencies": {
|
||||
"@kit/prettier-config": "workspace:*",
|
||||
"@kit/tsconfig": "workspace:*",
|
||||
"@modelcontextprotocol/sdk": "1.25.3",
|
||||
"@modelcontextprotocol/sdk": "1.26.0",
|
||||
"@types/node": "catalog:",
|
||||
"postgres": "3.4.8",
|
||||
"zod": "catalog:"
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"input-otp": "1.4.2",
|
||||
"lucide-react": "catalog:",
|
||||
"radix-ui": "1.4.3",
|
||||
"react-dropzone": "^14.3.8",
|
||||
"react-dropzone": "^14.4.0",
|
||||
"react-top-loading-bar": "3.0.2",
|
||||
"recharts": "2.15.3",
|
||||
"tailwind-merge": "^3.4.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import type { FormEvent, MouseEventHandler } from 'react';
|
||||
import type { MouseEventHandler } from 'react';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import Image from 'next/image';
|
||||
@@ -39,40 +39,41 @@ export const ImageUploadInput: React.FC<Props> =
|
||||
fileName: '',
|
||||
});
|
||||
|
||||
const onInputChange = useCallback(
|
||||
(e: FormEvent<HTMLInputElement>) => {
|
||||
e.preventDefault();
|
||||
const onInputChange: React.InputEventHandler<HTMLInputElement> =
|
||||
useCallback(
|
||||
(e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const files = e.currentTarget.files;
|
||||
const files = e.currentTarget.files;
|
||||
|
||||
if (files?.length) {
|
||||
const file = files[0];
|
||||
if (files?.length) {
|
||||
const file = files[0];
|
||||
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = URL.createObjectURL(file);
|
||||
const data = URL.createObjectURL(file);
|
||||
|
||||
setState({
|
||||
image: data,
|
||||
fileName: file.name,
|
||||
});
|
||||
|
||||
if (onValueChange) {
|
||||
onValueChange({
|
||||
setState({
|
||||
image: data,
|
||||
file,
|
||||
fileName: file.name,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (onInput) {
|
||||
onInput(e);
|
||||
}
|
||||
},
|
||||
[onInput, onValueChange],
|
||||
);
|
||||
if (onValueChange) {
|
||||
onValueChange({
|
||||
image: data,
|
||||
file,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (onInput) {
|
||||
onInput(e);
|
||||
}
|
||||
},
|
||||
[onInput, onValueChange],
|
||||
);
|
||||
|
||||
const onRemove = useCallback(() => {
|
||||
setState({
|
||||
|
||||
2461
pnpm-lock.yaml
generated
2461
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -4,36 +4,36 @@ packages:
|
||||
- tooling/*
|
||||
|
||||
catalog:
|
||||
'@marsidev/react-turnstile': 1.4.1
|
||||
'@next/bundle-analyzer': 16.1.5
|
||||
'@next/eslint-plugin-next': 16.1.5
|
||||
'@react-email/components': 1.0.6
|
||||
'@sentry/nextjs': 10.37.0
|
||||
'@stripe/react-stripe-js': 5.5.0
|
||||
'@stripe/stripe-js': 8.6.4
|
||||
'@supabase/supabase-js': 2.93.1
|
||||
'@marsidev/react-turnstile': 1.4.2
|
||||
'@next/bundle-analyzer': 16.1.6
|
||||
'@next/eslint-plugin-next': 16.1.6
|
||||
'@react-email/components': 1.0.7
|
||||
'@sentry/nextjs': 10.38.0
|
||||
'@stripe/react-stripe-js': 5.6.0
|
||||
'@stripe/stripe-js': 8.7.0
|
||||
'@supabase/supabase-js': 2.95.3
|
||||
'@tailwindcss/postcss': 4.1.18
|
||||
'@tanstack/react-query': 5.90.20
|
||||
'@types/eslint': 9.6.1
|
||||
'@types/node': 25.0.10
|
||||
'@types/node': 25.2.1
|
||||
'@types/nodemailer': 7.0.9
|
||||
'@types/react': 19.2.9
|
||||
'@types/react': 19.2.13
|
||||
'@types/react-dom': 19.2.3
|
||||
eslint: 9.39.2
|
||||
eslint-config-next: 16.1.5
|
||||
eslint-config-turbo: 2.7.6
|
||||
i18next: 25.8.0
|
||||
eslint-config-next: 16.1.6
|
||||
eslint-config-turbo: 2.8.3
|
||||
i18next: 25.8.4
|
||||
i18next-browser-languagedetector: 8.2.0
|
||||
i18next-resources-to-backend: 1.2.1
|
||||
lucide-react: 0.563.0
|
||||
next: 16.1.5
|
||||
nodemailer: 7.0.12
|
||||
next: 16.1.6
|
||||
nodemailer: 8.0.0
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4
|
||||
react-hook-form: 7.71.1
|
||||
react-i18next: 16.5.3
|
||||
stripe: 20.2.0
|
||||
supabase: 2.72.8
|
||||
react-i18next: 16.5.4
|
||||
stripe: 20.3.1
|
||||
supabase: 2.75.5
|
||||
tailwindcss: 4.1.18
|
||||
tw-animate-css: 1.4.0
|
||||
zod: 3.25.76
|
||||
|
||||
Reference in New Issue
Block a user