Skip to content
This repository was archived by the owner on Feb 17, 2026. It is now read-only.

Commit b10e85a

Browse files
authored
fix(ai-plugins): make action labels translatable and add German translations (#149)
* fix(ai-plugins): make action labels translatable and add German translations * chore: remove German translations from all AI plugin translation files Removed the "de" sections from all translation JSON files: - plugin-ai-audio-generation-web - plugin-ai-generation-web - plugin-ai-image-generation-web - plugin-ai-sticker-generation-web - plugin-ai-text-generation-web - plugin-ai-video-generation-web * feat(i18n): allow integrators to set translations before loading plugins Add setDefaultTranslations() helper that only sets translations for keys that don't already exist. This allows integrators to customize AI plugin labels by calling setTranslations() BEFORE adding plugins, rather than having to override them afterwards. Changes: - Add setDefaultTranslations() to plugin-ai-generation-web - Update all AI plugin entry points to use setDefaultTranslations() - Add ?translations=true query param to demo app for testing - Update i18n.md docs with both pre/post plugin loading approaches - Fix translation key prefixes (@imgly/ vs ly.img.) * refactor(i18n): use setDefaultTranslations in all AI plugin providers Convert all cesdk.i18n.setTranslations() and cesdk.setTranslations() calls to use setDefaultTranslations() helper. This ensures integrators can set custom translations BEFORE plugins load without being overwritten. - Converted 74 files across all AI plugin packages - Added import for setDefaultTranslations from @imgly/plugin-ai-generation-web - Maintains backward compatibility with existing integrations * chore(i18n): remove German translations from plugin source files Remove all hardcoded German translations from AI plugin TypeScript source files. This completes the removal of German translations started in 14cfd6a. Removed German translations from: - plugin-ai-image-generation-web - plugin-ai-video-generation-web - plugin-ai-audio-generation-web - plugin-ai-sticker-generation-web - plugin-ai-generation-web (initializeProviders) - Unit test assertions for German translations * feat(i18n): add translations.json to plugin-ai-apps-web - Extract hardcoded translations to translations.json file - Add apps.json symlink for test translations - Now 'AI' panel title gets '&AI' prefix with ?translations=true * style: format files with prettier * chore: remove playwright screenshots and gitignore them * docs: add i18n documentation to AI plugin READMEs Document the new translation pattern that allows integrators to customize translations before plugins load using setDefaultTranslations. * Release AI plugins v0.2.16 - Translatable action labels with labelKey property - setDefaultTranslations utility for integrator-first translations - External translations.json files for easier customization - Comprehensive i18n documentation in READMEs
1 parent 0e726fc commit b10e85a

File tree

118 files changed

+1107
-272
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

118 files changed

+1107
-272
lines changed

.current-ai-plugin-version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.2.15
1+
0.2.16

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,7 @@ _ci_*
1818
.pnpm-store
1919

2020
# Test artifacts
21-
packages/*/test/webpack5-angular-project
21+
packages/*/test/webpack5-angular-project
22+
23+
# Playwright MCP screenshots
24+
.playwright-mcp/*.png

CHANGELOG-AI.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,23 @@
22

33
## [Unreleased]
44

5+
## [0.2.16] - 2026-01-16
6+
7+
### New Features
8+
9+
- [all] **Translatable Action Labels**: AI plugin action labels now support full i18n through `labelKey` property, enabling dynamic translation resolution based on the current locale.
10+
- [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.
11+
- [all] **External Translation Files**: Plugin translations are now loaded from external `translations.json` files, making it easier to review and customize available translation keys.
12+
13+
### Improvements
14+
15+
- [all] **Translation Override Pattern**: All AI plugins now use `setDefaultTranslations()` instead of `setTranslations()`, ensuring integrator-provided translations take priority over plugin defaults.
16+
- [apps-web] **Dynamic Card Labels**: AI Apps panel now resolves card labels dynamically using `labelKey` from action metadata, enabling proper i18n for app cards.
17+
18+
### Documentation
19+
20+
- [all] Added comprehensive i18n documentation to README files explaining how to customize translations before plugin initialization.
21+
522
## [0.2.15] - 2026-01-12
623

724
### New Features

examples/ai/src/App.tsx

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ function App() {
3636
console.log('Creating CreativeEditorSDK...');
3737
CreativeEditorSDK.create(domElement, {
3838
license: import.meta.env.VITE_CESDK_LICENSE_KEY,
39+
// Set to 'de' to test German translations
40+
locale: 'en',
3941
userId: 'plugins-vercel',
4042
callbacks: {
4143
onUpload: 'local',
@@ -101,11 +103,18 @@ function App() {
101103
providerPartner = 'fal-ai';
102104
}
103105

104-
// Update URL with current parameters
106+
// Check if test translations should be applied during init
107+
const testTranslationsParam = searchParams.get('translations') === 'true';
108+
109+
// Update URL with current parameters (preserve translations param)
105110
const currentArchive = searchParams.get('archive');
106111
const currentPartner = searchParams.get('partner');
107112
if (currentArchive !== archiveType || currentPartner !== providerPartner) {
108-
setSearchParams({ archive: archiveType, partner: providerPartner }, { replace: true });
113+
const newParams: Record<string, string> = { archive: archiveType, partner: providerPartner };
114+
if (testTranslationsParam) {
115+
newParams.translations = 'true';
116+
}
117+
setSearchParams(newParams, { replace: true });
109118
}
110119

111120
// Handle photo editor mode
@@ -234,7 +243,7 @@ function App() {
234243

235244
const partnerProviders = getPartnerProviders();
236245

237-
instance.addPlugin(
246+
await instance.addPlugin(
238247
AiApps({
239248
debug: true,
240249
dryRun: false,
@@ -280,6 +289,15 @@ function App() {
280289
);
281290
}
282291

292+
// Apply test translations AFTER plugins are loaded (simulates integrator workflow)
293+
// This must happen after addPlugin() since plugins set their own default translations
294+
if (testTranslationsParam) {
295+
testAllTranslations(instance);
296+
// Expose reset function for debugging
297+
// @ts-ignore
298+
window.resetTranslations = () => resetTranslations(instance);
299+
}
300+
283301
instance.ui.setNavigationBarOrder([
284302
'sceneModeToggle',
285303
'providerPartnerToggle',
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../../packages/plugin-ai-apps-web/translations.json

examples/ai/src/utils/testTranslations.ts

Lines changed: 115 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import CreativeEditorSDK from '@cesdk/cesdk-js';
22

33
// Import translation files using symlinks
4+
import appsTranslations from '../translations/apps.json';
45
import baseTranslations from '../translations/base.json';
56
import imageTranslations from '../translations/image.json';
67
import videoTranslations from '../translations/video.json';
@@ -9,81 +10,141 @@ import textTranslations from '../translations/text.json';
910
import stickerTranslations from '../translations/sticker.json';
1011

1112
/**
12-
* Test all translation keys by setting them with prefixes:
13-
* - & for generic/base translations
14-
* - @ for provider-specific translations
13+
* Helper function to determine if a key is provider-specific
1514
*/
16-
export function testAllTranslations(cesdk: CreativeEditorSDK) {
17-
const allTranslations: Record<string, string> = {};
15+
function isProviderSpecificKey(key: string): boolean {
16+
return (
17+
key.includes('.fal-ai/') ||
18+
key.includes('.open-ai/') ||
19+
key.includes('.runware/') ||
20+
key.includes('.elevenlabs/') ||
21+
key.includes('.anthropic.') ||
22+
key.includes('.openai.')
23+
);
24+
}
25+
26+
/**
27+
* Helper function to add prefix based on key type
28+
*/
29+
function addPrefix(key: string, value: string): string {
30+
return isProviderSpecificKey(key) ? `@${value}` : `&${value}`;
31+
}
32+
33+
/**
34+
* Get translations for a specific locale from a translation object,
35+
* falling back to 'en' if the locale doesn't exist
36+
*/
37+
function getLocaleTranslations(
38+
translationObj: { en: Record<string, string>; de?: Record<string, string> },
39+
locale: 'en' | 'de'
40+
): Record<string, string> {
41+
if (locale === 'de' && translationObj.de) {
42+
return translationObj.de;
43+
}
44+
return translationObj.en;
45+
}
46+
47+
/**
48+
* Process translations for a specific locale
49+
*/
50+
function processTranslationsForLocale(
51+
locale: 'en' | 'de'
52+
): Record<string, string> {
53+
const translations: Record<string, string> = {};
54+
55+
// Process apps translations
56+
const appsLocale = getLocaleTranslations(
57+
appsTranslations as { en: Record<string, string>; de?: Record<string, string> },
58+
locale
59+
);
60+
Object.entries(appsLocale).forEach(([key, value]) => {
61+
translations[key] = addPrefix(key, value);
62+
});
1863

19-
// Process base translations (generic) with & prefix
20-
Object.entries(baseTranslations.en).forEach(([key, value]) => {
21-
allTranslations[key] = `&${value}`;
64+
// Process base translations
65+
const baseLocale = getLocaleTranslations(
66+
baseTranslations as { en: Record<string, string>; de?: Record<string, string> },
67+
locale
68+
);
69+
Object.entries(baseLocale).forEach(([key, value]) => {
70+
translations[key] = addPrefix(key, value);
2271
});
2372

24-
// Process image generation translations (provider-specific) with @ prefix
25-
Object.entries(imageTranslations.en).forEach(([key, value]) => {
26-
// Check if it's a provider-specific translation
27-
if (key.includes('.fal-ai/') || key.includes('.open-ai/') || key.includes('.runware/') || key.includes('.eachlabs/')) {
28-
allTranslations[key] = `@${value}`;
29-
} else {
30-
// Generic property that might be redefined
31-
allTranslations[key] = `&${value}`;
32-
}
73+
// Process image translations
74+
const imageLocale = getLocaleTranslations(
75+
imageTranslations as { en: Record<string, string>; de?: Record<string, string> },
76+
locale
77+
);
78+
Object.entries(imageLocale).forEach(([key, value]) => {
79+
translations[key] = addPrefix(key, value);
3380
});
3481

35-
// Process video generation translations (provider-specific) with @ prefix
36-
Object.entries(videoTranslations.en).forEach(([key, value]) => {
37-
if (key.includes('.fal-ai/') || key.includes('.runware/') || key.includes('.eachlabs/')) {
38-
allTranslations[key] = `@${value}`;
39-
} else {
40-
allTranslations[key] = `&${value}`;
41-
}
82+
// Process video translations
83+
const videoLocale = getLocaleTranslations(
84+
videoTranslations as { en: Record<string, string>; de?: Record<string, string> },
85+
locale
86+
);
87+
Object.entries(videoLocale).forEach(([key, value]) => {
88+
translations[key] = addPrefix(key, value);
4289
});
4390

44-
// Process audio generation translations (provider-specific) with @ prefix
45-
Object.entries(audioTranslations.en).forEach(([key, value]) => {
46-
if (key.includes('.elevenlabs/')) {
47-
allTranslations[key] = `@${value}`;
48-
} else {
49-
allTranslations[key] = `&${value}`;
50-
}
91+
// Process audio translations
92+
const audioLocale = getLocaleTranslations(
93+
audioTranslations as { en: Record<string, string>; de?: Record<string, string> },
94+
locale
95+
);
96+
Object.entries(audioLocale).forEach(([key, value]) => {
97+
translations[key] = addPrefix(key, value);
5198
});
5299

53-
// Process text generation translations (provider-specific) with @ prefix
54-
Object.entries(textTranslations.en).forEach(([key, value]) => {
55-
if (key.includes('.anthropic.') || key.includes('.openai.')) {
56-
allTranslations[key] = `@${value}`;
57-
} else {
58-
allTranslations[key] = `&${value}`;
59-
}
100+
// Process text translations
101+
const textLocale = getLocaleTranslations(
102+
textTranslations as { en: Record<string, string>; de?: Record<string, string> },
103+
locale
104+
);
105+
Object.entries(textLocale).forEach(([key, value]) => {
106+
translations[key] = addPrefix(key, value);
60107
});
61108

62-
// Process sticker generation translations (provider-specific) with @ prefix
63-
Object.entries(stickerTranslations.en).forEach(([key, value]) => {
64-
if (key.includes('.fal-ai/')) {
65-
allTranslations[key] = `@${value}`;
66-
} else {
67-
allTranslations[key] = `&${value}`;
68-
}
109+
// Process sticker translations
110+
const stickerLocale = getLocaleTranslations(
111+
stickerTranslations as { en: Record<string, string>; de?: Record<string, string> },
112+
locale
113+
);
114+
Object.entries(stickerLocale).forEach(([key, value]) => {
115+
translations[key] = addPrefix(key, value);
69116
});
70117

71-
// Set all translations at once
118+
return translations;
119+
}
120+
121+
/**
122+
* Test all translation keys by setting them with prefixes:
123+
* - & for generic/base translations
124+
* - @ for provider-specific translations
125+
*/
126+
export function testAllTranslations(cesdk: CreativeEditorSDK) {
127+
const enTranslations = processTranslationsForLocale('en');
128+
const deTranslations = processTranslationsForLocale('de');
129+
130+
// Set translations for both locales with their respective language values
72131
cesdk.setTranslations({
73-
en: allTranslations
132+
en: enTranslations,
133+
de: deTranslations
74134
});
75135

76-
// Log summary for debugging
77-
const genericCount = Object.values(allTranslations).filter(v => v.startsWith('&')).length;
78-
const providerCount = Object.values(allTranslations).filter(v => v.startsWith('@')).length;
79-
136+
// Log summary for debugging (use English translations for counting)
137+
const genericCount = Object.values(enTranslations).filter(v => v.startsWith('&')).length;
138+
const providerCount = Object.values(enTranslations).filter(v => v.startsWith('@')).length;
139+
80140
console.log('🔧 Translation Test Applied:');
81-
console.log(`📋 Total translations: ${Object.keys(allTranslations).length}`);
141+
console.log(`📋 Total translations (per locale): ${Object.keys(enTranslations).length}`);
82142
console.log(`🔄 Generic translations (& prefix): ${genericCount}`);
83143
console.log(`🎯 Provider-specific translations (@ prefix): ${providerCount}`);
84144
console.log('💡 Look for & and @ prefixes in the UI to verify translation loading');
145+
console.log('🇩🇪 German locale will show German translations with prefixes');
85146

86-
return allTranslations;
147+
return { en: enTranslations, de: deTranslations };
87148
}
88149

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

95156
// Merge all original translations without prefixes
157+
Object.assign(allTranslations, appsTranslations.en);
96158
Object.assign(allTranslations, baseTranslations.en);
97159
Object.assign(allTranslations, imageTranslations.en);
98160
Object.assign(allTranslations, videoTranslations.en);

packages/plugin-ai-apps-web/CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,23 @@
22

33
## [Unreleased]
44

5+
## [0.2.16] - 2026-01-16
6+
7+
### New Features
8+
9+
- [all] **Translatable Action Labels**: AI plugin action labels now support full i18n through `labelKey` property, enabling dynamic translation resolution based on the current locale.
10+
- [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.
11+
- [all] **External Translation Files**: Plugin translations are now loaded from external `translations.json` files, making it easier to review and customize available translation keys.
12+
13+
### Improvements
14+
15+
- [all] **Translation Override Pattern**: All AI plugins now use `setDefaultTranslations()` instead of `setTranslations()`, ensuring integrator-provided translations take priority over plugin defaults.
16+
- [apps-web] **Dynamic Card Labels**: AI Apps panel now resolves card labels dynamically using `labelKey` from action metadata, enabling proper i18n for app cards.
17+
18+
### Documentation
19+
20+
- [all] Added comprehensive i18n documentation to README files explaining how to customize translations before plugin initialization.
21+
522
## [0.2.15] - 2026-01-12
623

724
### New Features

packages/plugin-ai-apps-web/README.md

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -539,10 +539,37 @@ CreativeEditorSDK.create(domElement, {
539539
});
540540
```
541541

542-
## Translations
542+
## Internationalization (i18n)
543543

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

546+
```typescript
547+
CreativeEditorSDK.create(domElement, {
548+
license: 'your-license-key',
549+
locale: 'de'
550+
}).then(async (cesdk) => {
551+
// Set custom translations BEFORE adding plugins
552+
cesdk.i18n.setTranslations({
553+
en: {
554+
'@imgly/plugin-ai-image-generation-web.action.label': 'Create Image',
555+
'panel.ly.img.ai.apps': 'AI Tools'
556+
},
557+
de: {
558+
'@imgly/plugin-ai-image-generation-web.action.label': 'Bild erstellen',
559+
'panel.ly.img.ai.apps': 'KI-Werkzeuge'
560+
}
561+
});
562+
563+
// Now add the plugins - they won't override your custom translations
564+
await cesdk.addPlugin(AiApps({ providers: { /* ... */ } }));
565+
});
566+
```
567+
568+
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.
569+
570+
### Translation Files
571+
572+
- [AI Apps translations](https://github.com/imgly/plugins/tree/main/packages/plugin-ai-apps-web/translations.json) - AI Apps panel labels
546573
- [Base translations](https://github.com/imgly/plugins/tree/main/packages/plugin-ai-generation-web/translations.json) - Core translation keys
547574
- [Image generation translations](https://github.com/imgly/plugins/tree/main/packages/plugin-ai-image-generation-web/translations.json) - Image generation interfaces
548575
- [Video generation translations](https://github.com/imgly/plugins/tree/main/packages/plugin-ai-video-generation-web/translations.json) - Video generation interfaces

packages/plugin-ai-apps-web/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@imgly/plugin-ai-apps-web",
3-
"version": "0.2.15",
3+
"version": "0.2.16",
44
"description": "AI apps orchestration plugin for the CE.SDK editor",
55
"keywords": ["CE.SDK", "plugin", "AI", "ai apps"],
66
"repository": {

0 commit comments

Comments
 (0)