mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-01-23 02:36:05 +00:00
feat(plugins): add i18n support to procrastination-buster plugin
- Add comprehensive English translations (en.json) for all UI strings - Add useTranslate() hook for reactive translations - Update manifest.json with i18n configuration - Add i18n message handlers to plugin.ts - Create getProcrastinationTypes() for dynamic translation loading - Update App.tsx to use translations throughout - Update ProcrastinationInfo.tsx with full translation support - All 8 procrastination types, strategies, and info content now translatable The plugin now supports internationalization with complete English translations and can easily be extended to support additional languages.
This commit is contained in:
parent
eb120baf1b
commit
2550f91cb7
7 changed files with 428 additions and 49 deletions
108
packages/plugin-dev/procrastination-buster/i18n/en.json
Normal file
108
packages/plugin-dev/procrastination-buster/i18n/en.json
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
{
|
||||
"HOME": {
|
||||
"TITLE": "What's holding you back?",
|
||||
"SUBTITLE": "Choose what best matches your current feeling:",
|
||||
"LEARN_MORE_BUTTON": "Learn about procrastination →"
|
||||
},
|
||||
"NAVIGATION": {
|
||||
"BACK": "← Back"
|
||||
},
|
||||
"STRATEGIES": {
|
||||
"TITLE": "Recommended Strategies:",
|
||||
"ACTION_BUTTON": "🎯 Start focus session",
|
||||
"ACTION_BUTTON_TITLE": "Start a focus session"
|
||||
},
|
||||
"TYPES": {
|
||||
"OVERWHELM": {
|
||||
"TITLE": "Overwhelm",
|
||||
"EMOTION": "Too much at once",
|
||||
"STRATEGY_1": "Create micro-tasks (5 min steps)",
|
||||
"STRATEGY_2": "Implementation Intentions (If X, then Y)",
|
||||
"STRATEGY_3": "Pick just one thing",
|
||||
"STRATEGY_4": "Start Focus Session"
|
||||
},
|
||||
"PERFECTIONISM": {
|
||||
"TITLE": "Perfectionism",
|
||||
"EMOTION": "It's not perfect enough",
|
||||
"STRATEGY_1": "Time-box your first draft (30 min)",
|
||||
"STRATEGY_2": "Journal: What is \"good enough\"?",
|
||||
"STRATEGY_3": "Practice self-compassion",
|
||||
"STRATEGY_4": "Progress over perfection"
|
||||
},
|
||||
"UNCLEAR": {
|
||||
"TITLE": "Unclear",
|
||||
"EMOTION": "I don't know what to do",
|
||||
"STRATEGY_1": "Define next concrete step",
|
||||
"STRATEGY_2": "Talk to someone about it",
|
||||
"STRATEGY_3": "Create a mind map",
|
||||
"STRATEGY_4": "Write down questions"
|
||||
},
|
||||
"BORING": {
|
||||
"TITLE": "Boredom",
|
||||
"EMOTION": "It's boring",
|
||||
"STRATEGY_1": "Add gamification",
|
||||
"STRATEGY_2": "Combine with music/podcast",
|
||||
"STRATEGY_3": "Plan a reward",
|
||||
"STRATEGY_4": "Break into smaller parts"
|
||||
},
|
||||
"FEAR": {
|
||||
"TITLE": "Fear",
|
||||
"EMOTION": "I might fail",
|
||||
"STRATEGY_1": "Think through worst case",
|
||||
"STRATEGY_2": "Run small experiments",
|
||||
"STRATEGY_3": "Activate support system",
|
||||
"STRATEGY_4": "Document successes"
|
||||
},
|
||||
"ENERGY": {
|
||||
"TITLE": "Low Energy",
|
||||
"EMOTION": "I'm too tired",
|
||||
"STRATEGY_1": "5-minute movement break",
|
||||
"STRATEGY_2": "Drink water",
|
||||
"STRATEGY_3": "Easiest task first",
|
||||
"STRATEGY_4": "Power nap (20 min)"
|
||||
},
|
||||
"DISTRACTION": {
|
||||
"TITLE": "Distraction",
|
||||
"EMOTION": "Other things are more interesting",
|
||||
"STRATEGY_1": "Block distractions",
|
||||
"STRATEGY_2": "Schedule deep work block",
|
||||
"STRATEGY_3": "Clear work environment",
|
||||
"STRATEGY_4": "Start focus ritual"
|
||||
},
|
||||
"RESISTANCE": {
|
||||
"TITLE": "Resistance",
|
||||
"EMOTION": "I don't want to do this",
|
||||
"STRATEGY_1": "Why is it important?",
|
||||
"STRATEGY_2": "Pair with something pleasant",
|
||||
"STRATEGY_3": "Consider delegating",
|
||||
"STRATEGY_4": "Reframe: What will I learn?"
|
||||
}
|
||||
},
|
||||
"INFO": {
|
||||
"TITLE": "Understanding Procrastination",
|
||||
"INTRO": "Procrastination is an emotion regulation problem, not a time management problem.",
|
||||
"CYCLE_TITLE": "The Procrastination Cycle",
|
||||
"CYCLE_INTRO": "When we face tasks that trigger uncomfortable emotions, we enter a feedback loop:",
|
||||
"CYCLE_STEP_1": "Fear of failure",
|
||||
"CYCLE_STEP_2": "Avoid the task",
|
||||
"CYCLE_STEP_3": "Temporary relief",
|
||||
"CYCLE_STEP_4": "Increased anxiety",
|
||||
"BREAKING_TITLE": "Breaking the Cycle",
|
||||
"BREAKING_INTRO": "The key is to approach procrastination with curiosity and compassion, not judgment. Ask yourself:",
|
||||
"BREAKING_Q1": "What emotions come up when I think about this task?",
|
||||
"BREAKING_Q2": "What specific aspect feels most challenging?",
|
||||
"BREAKING_Q3": "What am I afraid might happen if I start?",
|
||||
"STRATEGIES_TITLE": "Practical Strategies",
|
||||
"STRATEGY_START_SMALL": "Start small:",
|
||||
"STRATEGY_START_SMALL_DESC": "What's the tiniest first step you could take?",
|
||||
"STRATEGY_TIMEBOX": "Time-box:",
|
||||
"STRATEGY_TIMEBOX_DESC": "Work for just 10-25 minutes, then take a break",
|
||||
"STRATEGY_REFRAME": "Reframe:",
|
||||
"STRATEGY_REFRAME_DESC": "Focus on progress over perfection",
|
||||
"STRATEGY_COMPASSION": "Self-compassion:",
|
||||
"STRATEGY_COMPASSION_DESC": "Speak to yourself as you would to a good friend",
|
||||
"TRIGGERS_TITLE": "Common Triggers",
|
||||
"TRIGGERS_TEXT": "Procrastination is often triggered by perfectionism, fear of failure, feeling overwhelmed, unclear expectations, or finding the task boring. Identifying your specific trigger is the first step to moving forward.",
|
||||
"BACK_TO_WORK": "Back to work!"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +1,48 @@
|
|||
import { Component, createSignal, Show, For } from 'solid-js';
|
||||
import { Component, createSignal, Show, For, createEffect, onMount } from 'solid-js';
|
||||
import {
|
||||
ProcrastinationType,
|
||||
procrastinationTypes,
|
||||
getProcrastinationTypes,
|
||||
PluginMessageType,
|
||||
WindowMessageType,
|
||||
} from './types';
|
||||
import { ProcrastinationInfo } from './ProcrastinationInfo';
|
||||
import { useTranslate } from './utils/useTranslate';
|
||||
import './App.css';
|
||||
|
||||
type ViewState = 'home' | 'info' | 'strategies';
|
||||
|
||||
const App: Component = () => {
|
||||
const t = useTranslate();
|
||||
const [currentView, setCurrentView] = createSignal<ViewState>('home');
|
||||
const [selectedType, setSelectedType] = createSignal<ProcrastinationType | null>(null);
|
||||
const [procrastinationTypes, setProcrastinationTypes] = createSignal<
|
||||
ProcrastinationType[]
|
||||
>([]);
|
||||
|
||||
// Translation signals
|
||||
const [homeTitle, setHomeTitle] = createSignal('');
|
||||
const [homeSubtitle, setHomeSubtitle] = createSignal('');
|
||||
const [learnMoreButton, setLearnMoreButton] = createSignal('');
|
||||
const [backButton, setBackButton] = createSignal('');
|
||||
const [strategiesTitle, setStrategiesTitle] = createSignal('');
|
||||
const [actionButton, setActionButton] = createSignal('');
|
||||
const [actionButtonTitle, setActionButtonTitle] = createSignal('');
|
||||
|
||||
// Load translations
|
||||
onMount(async () => {
|
||||
// Load UI translations
|
||||
setHomeTitle(await t('HOME.TITLE'));
|
||||
setHomeSubtitle(await t('HOME.SUBTITLE'));
|
||||
setLearnMoreButton(await t('HOME.LEARN_MORE_BUTTON'));
|
||||
setBackButton(await t('NAVIGATION.BACK'));
|
||||
setStrategiesTitle(await t('STRATEGIES.TITLE'));
|
||||
setActionButton(await t('STRATEGIES.ACTION_BUTTON'));
|
||||
setActionButtonTitle(await t('STRATEGIES.ACTION_BUTTON_TITLE'));
|
||||
|
||||
// Load procrastination types with translations
|
||||
const types = await getProcrastinationTypes(t);
|
||||
setProcrastinationTypes(types);
|
||||
});
|
||||
|
||||
const handleSelectType = (type: ProcrastinationType) => {
|
||||
setSelectedType(type);
|
||||
|
|
@ -48,7 +78,7 @@ const App: Component = () => {
|
|||
class="back-button"
|
||||
onClick={handleBack}
|
||||
>
|
||||
← Back
|
||||
{backButton()}
|
||||
</button>
|
||||
</header>
|
||||
</Show>
|
||||
|
|
@ -57,18 +87,18 @@ const App: Component = () => {
|
|||
{/* Home View */}
|
||||
<Show when={currentView() === 'home'}>
|
||||
<div class="intro page-fade">
|
||||
<h2>What's holding you back?</h2>
|
||||
<p class="text-muted">Choose what best matches your current feeling:</p>
|
||||
<h2>{homeTitle()}</h2>
|
||||
<p class="text-muted">{homeSubtitle()}</p>
|
||||
<button
|
||||
class="info-button"
|
||||
onClick={() => setCurrentView('info')}
|
||||
>
|
||||
Learn about procrastination →
|
||||
{learnMoreButton()}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="blocker-grid page-fade">
|
||||
<For each={procrastinationTypes}>
|
||||
<For each={procrastinationTypes()}>
|
||||
{(type) => (
|
||||
<button
|
||||
class="blocker-card card card-clickable"
|
||||
|
|
@ -97,7 +127,7 @@ const App: Component = () => {
|
|||
<p class="emotion text-muted">{selectedType()!.emotion}</p>
|
||||
</div>
|
||||
|
||||
<h3>Recommended Strategies:</h3>
|
||||
<h3>{strategiesTitle()}</h3>
|
||||
|
||||
<div class="strategy-list">
|
||||
<For each={selectedType()!.strategies}>
|
||||
|
|
@ -113,9 +143,9 @@ const App: Component = () => {
|
|||
<button
|
||||
class="strategy-action-btn"
|
||||
onClick={() => sendPluginMessage('START_POMODORO')}
|
||||
title="Start a focus session"
|
||||
title={actionButtonTitle()}
|
||||
>
|
||||
🎯 Start focus session
|
||||
{actionButton()}
|
||||
</button>
|
||||
</Show>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { Component } from 'solid-js';
|
||||
import { Component, createSignal, onMount } from 'solid-js';
|
||||
import { useTranslate } from './utils/useTranslate';
|
||||
import './App.css';
|
||||
|
||||
interface ProcrastinationInfoProps {
|
||||
|
|
@ -6,74 +7,118 @@ interface ProcrastinationInfoProps {
|
|||
}
|
||||
|
||||
export const ProcrastinationInfo: Component<ProcrastinationInfoProps> = (props) => {
|
||||
const t = useTranslate();
|
||||
|
||||
// Translation signals
|
||||
const [title, setTitle] = createSignal('');
|
||||
const [intro, setIntro] = createSignal('');
|
||||
const [cycleTitle, setCycleTitle] = createSignal('');
|
||||
const [cycleIntro, setCycleIntro] = createSignal('');
|
||||
const [cycleStep1, setCycleStep1] = createSignal('');
|
||||
const [cycleStep2, setCycleStep2] = createSignal('');
|
||||
const [cycleStep3, setCycleStep3] = createSignal('');
|
||||
const [cycleStep4, setCycleStep4] = createSignal('');
|
||||
const [breakingTitle, setBreakingTitle] = createSignal('');
|
||||
const [breakingIntro, setBreakingIntro] = createSignal('');
|
||||
const [breakingQ1, setBreakingQ1] = createSignal('');
|
||||
const [breakingQ2, setBreakingQ2] = createSignal('');
|
||||
const [breakingQ3, setBreakingQ3] = createSignal('');
|
||||
const [strategiesTitle, setStrategiesTitle] = createSignal('');
|
||||
const [strategyStartSmall, setStrategyStartSmall] = createSignal('');
|
||||
const [strategyStartSmallDesc, setStrategyStartSmallDesc] = createSignal('');
|
||||
const [strategyTimebox, setStrategyTimebox] = createSignal('');
|
||||
const [strategyTimeboxDesc, setStrategyTimeboxDesc] = createSignal('');
|
||||
const [strategyReframe, setStrategyReframe] = createSignal('');
|
||||
const [strategyReframeDesc, setStrategyReframeDesc] = createSignal('');
|
||||
const [strategyCompassion, setStrategyCompassion] = createSignal('');
|
||||
const [strategyCompassionDesc, setStrategyCompassionDesc] = createSignal('');
|
||||
const [triggersTitle, setTriggersTitle] = createSignal('');
|
||||
const [triggersText, setTriggersText] = createSignal('');
|
||||
const [backToWork, setBackToWork] = createSignal('');
|
||||
|
||||
// Load translations
|
||||
onMount(async () => {
|
||||
setTitle(await t('INFO.TITLE'));
|
||||
setIntro(await t('INFO.INTRO'));
|
||||
setCycleTitle(await t('INFO.CYCLE_TITLE'));
|
||||
setCycleIntro(await t('INFO.CYCLE_INTRO'));
|
||||
setCycleStep1(await t('INFO.CYCLE_STEP_1'));
|
||||
setCycleStep2(await t('INFO.CYCLE_STEP_2'));
|
||||
setCycleStep3(await t('INFO.CYCLE_STEP_3'));
|
||||
setCycleStep4(await t('INFO.CYCLE_STEP_4'));
|
||||
setBreakingTitle(await t('INFO.BREAKING_TITLE'));
|
||||
setBreakingIntro(await t('INFO.BREAKING_INTRO'));
|
||||
setBreakingQ1(await t('INFO.BREAKING_Q1'));
|
||||
setBreakingQ2(await t('INFO.BREAKING_Q2'));
|
||||
setBreakingQ3(await t('INFO.BREAKING_Q3'));
|
||||
setStrategiesTitle(await t('INFO.STRATEGIES_TITLE'));
|
||||
setStrategyStartSmall(await t('INFO.STRATEGY_START_SMALL'));
|
||||
setStrategyStartSmallDesc(await t('INFO.STRATEGY_START_SMALL_DESC'));
|
||||
setStrategyTimebox(await t('INFO.STRATEGY_TIMEBOX'));
|
||||
setStrategyTimeboxDesc(await t('INFO.STRATEGY_TIMEBOX_DESC'));
|
||||
setStrategyReframe(await t('INFO.STRATEGY_REFRAME'));
|
||||
setStrategyReframeDesc(await t('INFO.STRATEGY_REFRAME_DESC'));
|
||||
setStrategyCompassion(await t('INFO.STRATEGY_COMPASSION'));
|
||||
setStrategyCompassionDesc(await t('INFO.STRATEGY_COMPASSION_DESC'));
|
||||
setTriggersTitle(await t('INFO.TRIGGERS_TITLE'));
|
||||
setTriggersText(await t('INFO.TRIGGERS_TEXT'));
|
||||
setBackToWork(await t('INFO.BACK_TO_WORK'));
|
||||
});
|
||||
|
||||
return (
|
||||
<div class="page-fade info-content">
|
||||
<div class="intro">
|
||||
<h2>Understanding Procrastination</h2>
|
||||
<h2>{title()}</h2>
|
||||
<p>
|
||||
<strong>
|
||||
Procrastination is an emotion regulation problem, not a time management
|
||||
problem.
|
||||
</strong>
|
||||
<strong>{intro()}</strong>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<section>
|
||||
<h3>The Procrastination Cycle</h3>
|
||||
<p>
|
||||
When we face tasks that trigger uncomfortable emotions, we enter a feedback
|
||||
loop:
|
||||
</p>
|
||||
<h3>{cycleTitle()}</h3>
|
||||
<p>{cycleIntro()}</p>
|
||||
<div class="procrastination-graph">
|
||||
<div class="graph-item">Fear of failure</div>
|
||||
<div class="graph-item">{cycleStep1()}</div>
|
||||
<div class="sync-icon">→</div>
|
||||
<div class="graph-item">Avoid the task</div>
|
||||
<div class="graph-item">{cycleStep2()}</div>
|
||||
<div class="sync-icon">→</div>
|
||||
<div class="graph-item">Temporary relief</div>
|
||||
<div class="graph-item">{cycleStep3()}</div>
|
||||
<div class="sync-icon">→</div>
|
||||
<div class="graph-item">Increased anxiety</div>
|
||||
<div class="graph-item">{cycleStep4()}</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>Breaking the Cycle</h3>
|
||||
<p>
|
||||
The key is to approach procrastination with curiosity and compassion, not
|
||||
judgment. Ask yourself:
|
||||
</p>
|
||||
<h3>{breakingTitle()}</h3>
|
||||
<p>{breakingIntro()}</p>
|
||||
<ul>
|
||||
<li>What emotions come up when I think about this task?</li>
|
||||
<li>What specific aspect feels most challenging?</li>
|
||||
<li>What am I afraid might happen if I start?</li>
|
||||
<li>{breakingQ1()}</li>
|
||||
<li>{breakingQ2()}</li>
|
||||
<li>{breakingQ3()}</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>Practical Strategies</h3>
|
||||
<h3>{strategiesTitle()}</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Start small:</strong> What's the tiniest first step you could take?
|
||||
<strong>{strategyStartSmall()}</strong> {strategyStartSmallDesc()}
|
||||
</li>
|
||||
<li>
|
||||
<strong>Time-box:</strong> Work for just 10-25 minutes, then take a break
|
||||
<strong>{strategyTimebox()}</strong> {strategyTimeboxDesc()}
|
||||
</li>
|
||||
<li>
|
||||
<strong>Reframe:</strong> Focus on progress over perfection
|
||||
<strong>{strategyReframe()}</strong> {strategyReframeDesc()}
|
||||
</li>
|
||||
<li>
|
||||
<strong>Self-compassion:</strong> Speak to yourself as you would to a good
|
||||
friend
|
||||
<strong>{strategyCompassion()}</strong> {strategyCompassionDesc()}
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>Common Triggers</h3>
|
||||
<p>
|
||||
Procrastination is often triggered by perfectionism, fear of failure, feeling
|
||||
overwhelmed, unclear expectations, or finding the task boring. Identifying your
|
||||
specific trigger is the first step to moving forward.
|
||||
</p>
|
||||
<h3>{triggersTitle()}</h3>
|
||||
<p>{triggersText()}</p>
|
||||
</section>
|
||||
|
||||
<div class="action-buttons">
|
||||
|
|
@ -81,7 +126,7 @@ export const ProcrastinationInfo: Component<ProcrastinationInfoProps> = (props)
|
|||
class="primary-button"
|
||||
onClick={props.onBackToWork}
|
||||
>
|
||||
Back to work!
|
||||
{backToWork()}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -16,5 +16,8 @@
|
|||
"iFrame": true,
|
||||
"sidePanel": true,
|
||||
"isSkipMenuEntry": false,
|
||||
"icon": "icon.svg"
|
||||
"icon": "icon.svg",
|
||||
"i18n": {
|
||||
"languages": ["en"]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,43 @@
|
|||
// Procrastination Buster Plugin for Super Productivity
|
||||
// import { PluginInterface } from '@super-productivity/plugin-api';
|
||||
// declare const plugin: PluginInterface;
|
||||
import { PluginAPI } from '@super-productivity/plugin-api';
|
||||
import type { PluginHooks } from '@super-productivity/plugin-api';
|
||||
|
||||
declare const plugin: PluginAPI;
|
||||
|
||||
// Plugin initialization
|
||||
plugin.log.info('Procrastination Buster plugin initialized');
|
||||
|
||||
// Register side panel button
|
||||
plugin.registerSidePanelButton({
|
||||
icon: 'psychology',
|
||||
label: 'Procrastination Buster',
|
||||
onClick: () => {
|
||||
plugin.showIndexHtmlAsView();
|
||||
},
|
||||
});
|
||||
|
||||
// i18n support - handle translation requests from iframe
|
||||
if (plugin.onMessage) {
|
||||
plugin.onMessage(async (message: any) => {
|
||||
switch (message?.type) {
|
||||
case 'translate':
|
||||
return plugin.translate(message.data.key, message.data.params);
|
||||
case 'getCurrentLanguage':
|
||||
return plugin.getCurrentLanguage();
|
||||
default:
|
||||
return { error: 'Unknown message type' };
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Listen for language changes and notify iframe
|
||||
plugin.registerHook(PluginHooks.LANGUAGE_CHANGE, (language: string) => {
|
||||
// Notify the iframe about language change
|
||||
const iframe = document.querySelector('iframe[data-plugin-iframe]');
|
||||
if (iframe && (iframe as HTMLIFrameElement).contentWindow) {
|
||||
(iframe as HTMLIFrameElement).contentWindow!.postMessage(
|
||||
{ type: 'languageChanged', language },
|
||||
'*',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -43,9 +43,63 @@ export interface ProcrastinationType {
|
|||
// Data
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Type IDs for procrastination types - used to load translations
|
||||
*/
|
||||
export const PROCRASTINATION_TYPE_IDS = [
|
||||
'overwhelm',
|
||||
'perfectionism',
|
||||
'unclear',
|
||||
'boring',
|
||||
'fear',
|
||||
'energy',
|
||||
'distraction',
|
||||
'resistance',
|
||||
] as const;
|
||||
|
||||
export type ProcrastinationTypeId = (typeof PROCRASTINATION_TYPE_IDS)[number];
|
||||
|
||||
/**
|
||||
* Creates procrastination types from translation function
|
||||
* This allows the types to be translated dynamically
|
||||
*/
|
||||
export async function getProcrastinationTypes(
|
||||
t: (key: string, params?: Record<string, string | number>) => Promise<string>,
|
||||
): Promise<ProcrastinationType[]> {
|
||||
const types: ProcrastinationType[] = [];
|
||||
|
||||
for (const id of PROCRASTINATION_TYPE_IDS) {
|
||||
const typeKey = id.toUpperCase();
|
||||
|
||||
// Load all 4 strategies for this type
|
||||
const strategies: (string | Strategy)[] = [];
|
||||
for (let i = 1; i <= 4; i++) {
|
||||
const strategyText = await t(`TYPES.${typeKey}.STRATEGY_${i}`);
|
||||
|
||||
// Special cases with actions
|
||||
if ((id === 'overwhelm' && i === 4) || (id === 'distraction' && i === 2)) {
|
||||
strategies.push({ text: strategyText, action: true });
|
||||
} else {
|
||||
strategies.push(strategyText);
|
||||
}
|
||||
}
|
||||
|
||||
types.push({
|
||||
id,
|
||||
title: await t(`TYPES.${typeKey}.TITLE`),
|
||||
emotion: await t(`TYPES.${typeKey}.EMOTION`),
|
||||
strategies,
|
||||
});
|
||||
}
|
||||
|
||||
return types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Available procrastination types with their strategies.
|
||||
* Each type represents a common reason for procrastination.
|
||||
*
|
||||
* @deprecated Use getProcrastinationTypes() instead for i18n support
|
||||
*/
|
||||
export const procrastinationTypes: ProcrastinationType[] = [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,99 @@
|
|||
import { createSignal, createEffect, onCleanup } from 'solid-js';
|
||||
|
||||
// Communication with plugin.js
|
||||
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) => {
|
||||
if (event.data.messageId === messageId) {
|
||||
window.removeEventListener('message', handler);
|
||||
resolve(event.data.response);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('message', handler);
|
||||
window.parent.postMessage({ type, payload, 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');
|
||||
|
||||
// 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 });
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue