Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .current-ai-plugin-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.2.15
0.2.16
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,7 @@ _ci_*
.pnpm-store

# Test artifacts
packages/*/test/webpack5-angular-project
packages/*/test/webpack5-angular-project

# Playwright MCP screenshots
.playwright-mcp/*.png
17 changes: 17 additions & 0 deletions CHANGELOG-AI.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,23 @@

## [Unreleased]

## [0.2.16] - 2026-01-16

### New Features

- [all] **Translatable Action Labels**: AI plugin action labels now support full i18n through `labelKey` property, enabling dynamic translation resolution based on the current locale.
- [generation-web] **setDefaultTranslations Utility**: Added `setDefaultTranslations()` function that only sets translation keys that don't already exist, allowing integrators to customize translations before plugins load without being overwritten.
- [all] **External Translation Files**: Plugin translations are now loaded from external `translations.json` files, making it easier to review and customize available translation keys.

### Improvements

- [all] **Translation Override Pattern**: All AI plugins now use `setDefaultTranslations()` instead of `setTranslations()`, ensuring integrator-provided translations take priority over plugin defaults.
- [apps-web] **Dynamic Card Labels**: AI Apps panel now resolves card labels dynamically using `labelKey` from action metadata, enabling proper i18n for app cards.

### Documentation

- [all] Added comprehensive i18n documentation to README files explaining how to customize translations before plugin initialization.

## [0.2.15] - 2026-01-12

### New Features
Expand Down
24 changes: 21 additions & 3 deletions examples/ai/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ function App() {
console.log('Creating CreativeEditorSDK...');
CreativeEditorSDK.create(domElement, {
license: import.meta.env.VITE_CESDK_LICENSE_KEY,
// Set to 'de' to test German translations
locale: 'en',
userId: 'plugins-vercel',
callbacks: {
onUpload: 'local',
Expand Down Expand Up @@ -101,11 +103,18 @@ function App() {
providerPartner = 'fal-ai';
}

// Update URL with current parameters
// Check if test translations should be applied during init
const testTranslationsParam = searchParams.get('translations') === 'true';

// Update URL with current parameters (preserve translations param)
const currentArchive = searchParams.get('archive');
const currentPartner = searchParams.get('partner');
if (currentArchive !== archiveType || currentPartner !== providerPartner) {
setSearchParams({ archive: archiveType, partner: providerPartner }, { replace: true });
const newParams: Record<string, string> = { archive: archiveType, partner: providerPartner };
if (testTranslationsParam) {
newParams.translations = 'true';
}
setSearchParams(newParams, { replace: true });
}

// Handle photo editor mode
Expand Down Expand Up @@ -234,7 +243,7 @@ function App() {

const partnerProviders = getPartnerProviders();

instance.addPlugin(
await instance.addPlugin(
AiApps({
debug: true,
dryRun: false,
Expand Down Expand Up @@ -280,6 +289,15 @@ function App() {
);
}

// Apply test translations AFTER plugins are loaded (simulates integrator workflow)
// This must happen after addPlugin() since plugins set their own default translations
if (testTranslationsParam) {
testAllTranslations(instance);
// Expose reset function for debugging
// @ts-ignore
window.resetTranslations = () => resetTranslations(instance);
}

instance.ui.setNavigationBarOrder([
'sceneModeToggle',
'providerPartnerToggle',
Expand Down
1 change: 1 addition & 0 deletions examples/ai/src/translations/apps.json
168 changes: 115 additions & 53 deletions examples/ai/src/utils/testTranslations.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import CreativeEditorSDK from '@cesdk/cesdk-js';

// Import translation files using symlinks
import appsTranslations from '../translations/apps.json';
import baseTranslations from '../translations/base.json';
import imageTranslations from '../translations/image.json';
import videoTranslations from '../translations/video.json';
Expand All @@ -9,81 +10,141 @@ import textTranslations from '../translations/text.json';
import stickerTranslations from '../translations/sticker.json';

/**
* Test all translation keys by setting them with prefixes:
* - & for generic/base translations
* - @ for provider-specific translations
* Helper function to determine if a key is provider-specific
*/
export function testAllTranslations(cesdk: CreativeEditorSDK) {
const allTranslations: Record<string, string> = {};
function isProviderSpecificKey(key: string): boolean {
return (
key.includes('.fal-ai/') ||
key.includes('.open-ai/') ||
key.includes('.runware/') ||
key.includes('.elevenlabs/') ||
key.includes('.anthropic.') ||
key.includes('.openai.')
);
}

/**
* Helper function to add prefix based on key type
*/
function addPrefix(key: string, value: string): string {
return isProviderSpecificKey(key) ? `@${value}` : `&${value}`;
}

/**
* Get translations for a specific locale from a translation object,
* falling back to 'en' if the locale doesn't exist
*/
function getLocaleTranslations(
translationObj: { en: Record<string, string>; de?: Record<string, string> },
locale: 'en' | 'de'
): Record<string, string> {
if (locale === 'de' && translationObj.de) {
return translationObj.de;
}
return translationObj.en;
}

/**
* Process translations for a specific locale
*/
function processTranslationsForLocale(
locale: 'en' | 'de'
): Record<string, string> {
const translations: Record<string, string> = {};

// Process apps translations
const appsLocale = getLocaleTranslations(
appsTranslations as { en: Record<string, string>; de?: Record<string, string> },
locale
);
Object.entries(appsLocale).forEach(([key, value]) => {
translations[key] = addPrefix(key, value);
});

// Process base translations (generic) with & prefix
Object.entries(baseTranslations.en).forEach(([key, value]) => {
allTranslations[key] = `&${value}`;
// Process base translations
const baseLocale = getLocaleTranslations(
baseTranslations as { en: Record<string, string>; de?: Record<string, string> },
locale
);
Object.entries(baseLocale).forEach(([key, value]) => {
translations[key] = addPrefix(key, value);
});

// Process image generation translations (provider-specific) with @ prefix
Object.entries(imageTranslations.en).forEach(([key, value]) => {
// Check if it's a provider-specific translation
if (key.includes('.fal-ai/') || key.includes('.open-ai/') || key.includes('.runware/') || key.includes('.eachlabs/')) {
allTranslations[key] = `@${value}`;
} else {
// Generic property that might be redefined
allTranslations[key] = `&${value}`;
}
// Process image translations
const imageLocale = getLocaleTranslations(
imageTranslations as { en: Record<string, string>; de?: Record<string, string> },
locale
);
Object.entries(imageLocale).forEach(([key, value]) => {
translations[key] = addPrefix(key, value);
});

// Process video generation translations (provider-specific) with @ prefix
Object.entries(videoTranslations.en).forEach(([key, value]) => {
if (key.includes('.fal-ai/') || key.includes('.runware/') || key.includes('.eachlabs/')) {
allTranslations[key] = `@${value}`;
} else {
allTranslations[key] = `&${value}`;
}
// Process video translations
const videoLocale = getLocaleTranslations(
videoTranslations as { en: Record<string, string>; de?: Record<string, string> },
locale
);
Object.entries(videoLocale).forEach(([key, value]) => {
translations[key] = addPrefix(key, value);
});

// Process audio generation translations (provider-specific) with @ prefix
Object.entries(audioTranslations.en).forEach(([key, value]) => {
if (key.includes('.elevenlabs/')) {
allTranslations[key] = `@${value}`;
} else {
allTranslations[key] = `&${value}`;
}
// Process audio translations
const audioLocale = getLocaleTranslations(
audioTranslations as { en: Record<string, string>; de?: Record<string, string> },
locale
);
Object.entries(audioLocale).forEach(([key, value]) => {
translations[key] = addPrefix(key, value);
});

// Process text generation translations (provider-specific) with @ prefix
Object.entries(textTranslations.en).forEach(([key, value]) => {
if (key.includes('.anthropic.') || key.includes('.openai.')) {
allTranslations[key] = `@${value}`;
} else {
allTranslations[key] = `&${value}`;
}
// Process text translations
const textLocale = getLocaleTranslations(
textTranslations as { en: Record<string, string>; de?: Record<string, string> },
locale
);
Object.entries(textLocale).forEach(([key, value]) => {
translations[key] = addPrefix(key, value);
});

// Process sticker generation translations (provider-specific) with @ prefix
Object.entries(stickerTranslations.en).forEach(([key, value]) => {
if (key.includes('.fal-ai/')) {
allTranslations[key] = `@${value}`;
} else {
allTranslations[key] = `&${value}`;
}
// Process sticker translations
const stickerLocale = getLocaleTranslations(
stickerTranslations as { en: Record<string, string>; de?: Record<string, string> },
locale
);
Object.entries(stickerLocale).forEach(([key, value]) => {
translations[key] = addPrefix(key, value);
});

// Set all translations at once
return translations;
}

/**
* Test all translation keys by setting them with prefixes:
* - & for generic/base translations
* - @ for provider-specific translations
*/
export function testAllTranslations(cesdk: CreativeEditorSDK) {
const enTranslations = processTranslationsForLocale('en');
const deTranslations = processTranslationsForLocale('de');

// Set translations for both locales with their respective language values
cesdk.setTranslations({
en: allTranslations
en: enTranslations,
de: deTranslations
});

// Log summary for debugging
const genericCount = Object.values(allTranslations).filter(v => v.startsWith('&')).length;
const providerCount = Object.values(allTranslations).filter(v => v.startsWith('@')).length;
// Log summary for debugging (use English translations for counting)
const genericCount = Object.values(enTranslations).filter(v => v.startsWith('&')).length;
const providerCount = Object.values(enTranslations).filter(v => v.startsWith('@')).length;

console.log('🔧 Translation Test Applied:');
console.log(`📋 Total translations: ${Object.keys(allTranslations).length}`);
console.log(`📋 Total translations (per locale): ${Object.keys(enTranslations).length}`);
console.log(`🔄 Generic translations (& prefix): ${genericCount}`);
console.log(`🎯 Provider-specific translations (@ prefix): ${providerCount}`);
console.log('💡 Look for & and @ prefixes in the UI to verify translation loading');
console.log('🇩🇪 German locale will show German translations with prefixes');

return allTranslations;
return { en: enTranslations, de: deTranslations };
}

/**
Expand All @@ -93,6 +154,7 @@ export function resetTranslations(cesdk: CreativeEditorSDK) {
const allTranslations: Record<string, string> = {};

// Merge all original translations without prefixes
Object.assign(allTranslations, appsTranslations.en);
Object.assign(allTranslations, baseTranslations.en);
Object.assign(allTranslations, imageTranslations.en);
Object.assign(allTranslations, videoTranslations.en);
Expand Down
17 changes: 17 additions & 0 deletions packages/plugin-ai-apps-web/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,23 @@

## [Unreleased]

## [0.2.16] - 2026-01-16

### New Features

- [all] **Translatable Action Labels**: AI plugin action labels now support full i18n through `labelKey` property, enabling dynamic translation resolution based on the current locale.
- [generation-web] **setDefaultTranslations Utility**: Added `setDefaultTranslations()` function that only sets translation keys that don't already exist, allowing integrators to customize translations before plugins load without being overwritten.
- [all] **External Translation Files**: Plugin translations are now loaded from external `translations.json` files, making it easier to review and customize available translation keys.

### Improvements

- [all] **Translation Override Pattern**: All AI plugins now use `setDefaultTranslations()` instead of `setTranslations()`, ensuring integrator-provided translations take priority over plugin defaults.
- [apps-web] **Dynamic Card Labels**: AI Apps panel now resolves card labels dynamically using `labelKey` from action metadata, enabling proper i18n for app cards.

### Documentation

- [all] Added comprehensive i18n documentation to README files explaining how to customize translations before plugin initialization.

## [0.2.15] - 2026-01-12

### New Features
Expand Down
31 changes: 29 additions & 2 deletions packages/plugin-ai-apps-web/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -539,10 +539,37 @@ CreativeEditorSDK.create(domElement, {
});
```

## Translations
## Internationalization (i18n)

The AI Apps plugin uses translations from individual AI generation plugins. For customization, refer to the respective translation files:
The AI Apps plugin supports full internationalization. To customize translations, set them **before** adding the plugin:

```typescript
CreativeEditorSDK.create(domElement, {
license: 'your-license-key',
locale: 'de'
}).then(async (cesdk) => {
// Set custom translations BEFORE adding plugins
cesdk.i18n.setTranslations({
en: {
'@imgly/plugin-ai-image-generation-web.action.label': 'Create Image',
'panel.ly.img.ai.apps': 'AI Tools'
},
de: {
'@imgly/plugin-ai-image-generation-web.action.label': 'Bild erstellen',
'panel.ly.img.ai.apps': 'KI-Werkzeuge'
}
});

// Now add the plugins - they won't override your custom translations
await cesdk.addPlugin(AiApps({ providers: { /* ... */ } }));
});
```

For detailed documentation on the translation system, including all available translation keys and utilities, see the [Internationalization section](https://github.com/imgly/plugins/tree/main/packages/plugin-ai-generation-web#internationalization-i18n) in the core AI generation package.

### Translation Files

- [AI Apps translations](https://github.com/imgly/plugins/tree/main/packages/plugin-ai-apps-web/translations.json) - AI Apps panel labels
- [Base translations](https://github.com/imgly/plugins/tree/main/packages/plugin-ai-generation-web/translations.json) - Core translation keys
- [Image generation translations](https://github.com/imgly/plugins/tree/main/packages/plugin-ai-image-generation-web/translations.json) - Image generation interfaces
- [Video generation translations](https://github.com/imgly/plugins/tree/main/packages/plugin-ai-video-generation-web/translations.json) - Video generation interfaces
Expand Down
2 changes: 1 addition & 1 deletion packages/plugin-ai-apps-web/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@imgly/plugin-ai-apps-web",
"version": "0.2.15",
"version": "0.2.16",
"description": "AI apps orchestration plugin for the CE.SDK editor",
"keywords": ["CE.SDK", "plugin", "AI", "ai apps"],
"repository": {
Expand Down
Loading