super-productivity/packages/plugin-dev/boilerplate-solid-js/src/utils/useTranslate.ts
Johannes Millan 9be6bf7c27 fix(plugins): initialize current language on plugin mount
Fixed i18n to fetch and use the current language when the plugin
initializes, instead of always defaulting to English. Now the plugin
will show the correct language immediately on first load, and also
update reactively when the language changes.

Changes:
- Added getCurrentLanguage() call in useTranslate initialization
- Changed onMount to createEffect for reactive language loading
- Updated both App.tsx and ProcrastinationInfo.tsx to use createEffect
- Applied fixes to both procrastination-buster and boilerplate
- Added debug logging to troubleshoot language change events

This fixes the issue where plugins always started in English regardless
of the user's selected language.
2026-01-16 22:34:50 +01:00

111 lines
3.3 KiB
TypeScript

import { createSignal, createEffect, onCleanup } from 'solid-js';
// Communication with plugin.js via the PluginAPI.onMessage system
const sendMessage = async (type: string, payload?: any): Promise<any> => {
return new Promise((resolve) => {
const messageId = Math.random().toString(36).substring(2, 9);
const handler = (event: MessageEvent) => {
// Listen for MESSAGE_RESPONSE from parent
if (event.data.type === 'PLUGIN_MESSAGE_RESPONSE' && event.data.messageId === messageId) {
window.removeEventListener('message', handler);
resolve(event.data.result);
}
};
window.addEventListener('message', handler);
const message = { type, payload };
// Use the proper PLUGIN_MESSAGE type for the plugin message system
window.parent.postMessage({ type: 'PLUGIN_MESSAGE', message, messageId }, '*');
});
};
/**
* SolidJS hook for reactive translations
*
* This hook provides a simple way to translate strings in your plugin components.
* It automatically handles language changes and caches translations for performance.
*
* @example
* ```tsx
* function MyComponent() {
* const t = useTranslate();
* const [greeting, setGreeting] = createSignal('');
*
* createEffect(async () => {
* setGreeting(await t('GREETING'));
* });
*
* return <h1>{greeting()}</h1>;
* }
* ```
*
* @example With parameters
* ```tsx
* function TaskCount() {
* const t = useTranslate();
* const [message, setMessage] = createSignal('');
*
* createEffect(async () => {
* setMessage(await t('TASK.CREATED_SUCCESS', { title: 'My Task' }));
* });
*
* return <p>{message()}</p>;
* }
* ```
*/
export function useTranslate() {
const [currentLanguage, setCurrentLanguage] = createSignal<string>('en');
// Initialize current language on mount
createEffect(() => {
(async () => {
try {
const lang = await sendMessage('getCurrentLanguage');
setCurrentLanguage(lang);
} catch (error) {
console.error('[useTranslate] Failed to get current language:', error);
}
})();
});
// Listen for language change events
createEffect(() => {
const handleLanguageChange = (event: MessageEvent) => {
if (event.data.type === 'languageChanged') {
setCurrentLanguage(event.data.language);
}
};
window.addEventListener('message', handleLanguageChange);
onCleanup(() => {
window.removeEventListener('message', handleLanguageChange);
});
});
/**
* Translate a key with optional parameter interpolation
*
* @param key - Translation key (supports dot notation for nested keys)
* @param params - Optional parameters for interpolation (e.g., { count: 5 })
* @returns Promise that resolves to the translated string
*
* @example
* ```tsx
* const greeting = await t('APP.TITLE');
* const message = await t('TASK.COUNT', { count: 5 });
* ```
*/
const t = async (key: string, params?: Record<string, string | number>): Promise<string> => {
try {
return await sendMessage('translate', { key, params });
} catch (error) {
console.error('Translation error:', error);
return key;
}
};
// Also expose the current language as a signal for reactive language-dependent logic
return Object.assign(t, { currentLanguage });
}