mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-01-23 02:36:05 +00:00
feat(plugins): add i18n API methods and fix LANGUAGE_CHANGE hook
Phase 4-5: Plugin API Extensions and Language Switching - Add translate(), formatDate(), getCurrentLanguage() to PluginAPI - Inject PluginI18nService into PluginAPI constructor - Update PluginRunner to pass PluginI18nService to PluginAPI - Fix LANGUAGE_CHANGE hook bug (was firing on work context changes) - Add proper languageChange$ effect listening to global config updates - Wire language changes to PluginI18nService.setCurrentLanguage() - Remove incorrect workContextChange$ effect dispatch Translation API features: - Simple translate(key, params?) with fallback chain - Locale-aware formatDate(date, format) with predefined formats - getCurrentLanguage() to get current app language Language switching: - Listens to updateGlobalConfigSection for 'localization' section - Uses distinctUntilChanged to fire only on actual language changes - Updates plugin i18n service and dispatches hook to plugins
This commit is contained in:
parent
cde660bd0c
commit
c742295624
3 changed files with 65 additions and 8 deletions
|
|
@ -23,6 +23,8 @@ import {
|
|||
} from '@super-productivity/plugin-api';
|
||||
import { PluginBridgeService } from './plugin-bridge.service';
|
||||
import { PluginLog } from '../core/log';
|
||||
import { PluginI18nService } from './plugin-i18n.service';
|
||||
import { formatDateForPlugin } from './plugin-i18n-date.util';
|
||||
import {
|
||||
projectCopyToProjectData,
|
||||
projectDataToPartialProjectCopy,
|
||||
|
|
@ -62,6 +64,7 @@ export class PluginAPI implements PluginAPIInterface {
|
|||
public cfg: PluginBaseCfg,
|
||||
private _pluginId: string,
|
||||
private _pluginBridge: PluginBridgeService,
|
||||
private _pluginI18nService: PluginI18nService,
|
||||
private _manifest?: PluginManifest,
|
||||
) {
|
||||
// Get bound methods for this plugin
|
||||
|
|
@ -476,6 +479,33 @@ export class PluginAPI implements PluginAPIInterface {
|
|||
return this._pluginBridge.setSimpleCounterDate(id, date, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate a key using plugin's translation files
|
||||
* Falls back to English, then to the key itself if not found
|
||||
*/
|
||||
translate(key: string, params?: Record<string, string | number>): string {
|
||||
return this._pluginI18nService.translate(this._pluginId, key, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a date according to predefined format and current locale
|
||||
* Supports: 'short', 'medium', 'long', 'time', 'datetime'
|
||||
*/
|
||||
formatDate(
|
||||
date: Date | string | number,
|
||||
format: 'short' | 'medium' | 'long' | 'time' | 'datetime',
|
||||
): string {
|
||||
const locale = this._pluginI18nService.getCurrentLanguage();
|
||||
return formatDateForPlugin(date, format, locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current app language code
|
||||
*/
|
||||
getCurrentLanguage(): string {
|
||||
return this._pluginI18nService.getCurrentLanguage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up all resources associated with this plugin API instance
|
||||
* Called when the plugin is being unloaded
|
||||
|
|
|
|||
|
|
@ -1,7 +1,15 @@
|
|||
import { inject, Injectable } from '@angular/core';
|
||||
import { createEffect, ofType } from '@ngrx/effects';
|
||||
import { select, Store } from '@ngrx/store';
|
||||
import { filter, map, switchMap, take, tap, withLatestFrom } from 'rxjs/operators';
|
||||
import {
|
||||
distinctUntilChanged,
|
||||
filter,
|
||||
map,
|
||||
switchMap,
|
||||
take,
|
||||
tap,
|
||||
withLatestFrom,
|
||||
} from 'rxjs/operators';
|
||||
import { EMPTY } from 'rxjs';
|
||||
|
||||
import {
|
||||
|
|
@ -9,10 +17,12 @@ import {
|
|||
selectTaskFeatureState,
|
||||
} from '../features/tasks/store/task.selectors';
|
||||
import { selectProjectFeatureState } from '../features/project/store/project.selectors';
|
||||
import { selectLocalizationConfig } from '../features/config/store/global-config.reducer';
|
||||
import { updateGlobalConfigSection } from '../features/config/store/global-config.actions';
|
||||
import { Task } from '../features/tasks/task.model';
|
||||
import { PluginService } from './plugin.service';
|
||||
import { PluginHooks } from './plugin-api.model';
|
||||
import { setActiveWorkContext } from '../features/work-context/store/work-context.actions';
|
||||
import { PluginI18nService } from './plugin-i18n.service';
|
||||
import { TaskSharedActions } from '../root-store/meta/task-shared.actions';
|
||||
import {
|
||||
setCurrentTask,
|
||||
|
|
@ -41,6 +51,7 @@ export class PluginHooksEffects {
|
|||
private readonly actions$ = inject(LOCAL_ACTIONS);
|
||||
private readonly store = inject(Store);
|
||||
private readonly pluginService = inject(PluginService);
|
||||
private readonly pluginI18nService = inject(PluginI18nService);
|
||||
|
||||
taskComplete$ = createEffect(
|
||||
() =>
|
||||
|
|
@ -191,14 +202,22 @@ export class PluginHooksEffects {
|
|||
{ dispatch: false },
|
||||
);
|
||||
|
||||
workContextChange$ = createEffect(
|
||||
// Language change effect - listens to actual language config changes
|
||||
languageChange$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType(setActiveWorkContext),
|
||||
tap((action) => {
|
||||
ofType(updateGlobalConfigSection),
|
||||
filter((action) => action.sectionKey === 'localization'),
|
||||
withLatestFrom(this.store.pipe(select(selectLocalizationConfig))),
|
||||
map(([_, localizationConfig]) => localizationConfig.lng),
|
||||
distinctUntilChanged(),
|
||||
tap((newLanguage) => {
|
||||
// Update plugin i18n service with new language
|
||||
this.pluginI18nService.setCurrentLanguage(newLanguage);
|
||||
|
||||
// Dispatch hook to notify plugins
|
||||
this.pluginService.dispatchHook(PluginHooks.LANGUAGE_CHANGE, {
|
||||
activeId: action.activeId,
|
||||
activeType: action.activeType,
|
||||
newLanguage,
|
||||
});
|
||||
}),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { PluginManifest, PluginBaseCfg, PluginInstance } from './plugin-api.mode
|
|||
import { PluginAPI } from './plugin-api';
|
||||
import { PluginBridgeService } from './plugin-bridge.service';
|
||||
import { PluginSecurityService } from './plugin-security';
|
||||
import { PluginI18nService } from './plugin-i18n.service';
|
||||
import { SnackService } from '../core/snack/snack.service';
|
||||
import { PluginCleanupService } from './plugin-cleanup.service';
|
||||
import { PluginLog } from '../core/log';
|
||||
|
|
@ -17,6 +18,7 @@ import { PluginLog } from '../core/log';
|
|||
export class PluginRunner {
|
||||
private _pluginBridge = inject(PluginBridgeService);
|
||||
private _securityService = inject(PluginSecurityService);
|
||||
private _pluginI18nService = inject(PluginI18nService);
|
||||
private _snackService = inject(SnackService);
|
||||
private _cleanupService = inject(PluginCleanupService);
|
||||
|
||||
|
|
@ -34,7 +36,13 @@ export class PluginRunner {
|
|||
): Promise<PluginInstance> {
|
||||
try {
|
||||
// Create plugin API
|
||||
const pluginAPI = new PluginAPI(baseCfg, manifest.id, this._pluginBridge, manifest);
|
||||
const pluginAPI = new PluginAPI(
|
||||
baseCfg,
|
||||
manifest.id,
|
||||
this._pluginBridge,
|
||||
this._pluginI18nService,
|
||||
manifest,
|
||||
);
|
||||
|
||||
// executeNodeScript is now automatically bound if permitted via createBoundMethods
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue