mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-23 21:56:43 +02:00
✨ feat(matrix-bots): add i18n system and direct message fallback
- Add I18nService with per-user language preferences (de/en) - Add !language/!sprache command to all 4 bots (todo, calendar, contacts, clock) - Add fallback behavior: messages without commands create tasks/events/contacts/timers - Improve clock bot duration parsing to accept bare numbers as minutes (e.g. "25" = 25min) - Add support for more duration formats: "25 minuten", "1 stunde", etc. Language preferences stored in SessionService, default configurable via BOT_DEFAULT_LANGUAGE env var.
This commit is contained in:
parent
5c688d713e
commit
c2c80efc50
17 changed files with 1626 additions and 18 deletions
|
|
@ -38,6 +38,10 @@
|
||||||
"types": "./dist/credit/index.d.ts",
|
"types": "./dist/credit/index.d.ts",
|
||||||
"default": "./dist/credit/index.js"
|
"default": "./dist/credit/index.js"
|
||||||
},
|
},
|
||||||
|
"./i18n": {
|
||||||
|
"types": "./dist/i18n/index.d.ts",
|
||||||
|
"default": "./dist/i18n/index.js"
|
||||||
|
},
|
||||||
"./nutrition": {
|
"./nutrition": {
|
||||||
"types": "./dist/nutrition/index.d.ts",
|
"types": "./dist/nutrition/index.d.ts",
|
||||||
"default": "./dist/nutrition/index.js"
|
"default": "./dist/nutrition/index.js"
|
||||||
|
|
|
||||||
68
packages/bot-services/src/i18n/i18n.module.ts
Normal file
68
packages/bot-services/src/i18n/i18n.module.ts
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
import { Module, DynamicModule, Global } from '@nestjs/common';
|
||||||
|
import { ConfigModule } from '@nestjs/config';
|
||||||
|
import { I18nService, I18N_OPTIONS } from './i18n.service';
|
||||||
|
import { I18nOptions } from './types';
|
||||||
|
import { SessionModule } from '../session/session.module';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* I18n Module for Matrix Bots
|
||||||
|
*
|
||||||
|
* Provides multi-language support with per-user language preferences.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* // Basic usage (uses SessionModule and ConfigModule)
|
||||||
|
* @Module({
|
||||||
|
* imports: [I18nModule.forRoot()],
|
||||||
|
* })
|
||||||
|
*
|
||||||
|
* // With custom default language
|
||||||
|
* @Module({
|
||||||
|
* imports: [I18nModule.forRoot({ defaultLanguage: 'en' })],
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
@Global()
|
||||||
|
@Module({})
|
||||||
|
export class I18nModule {
|
||||||
|
/**
|
||||||
|
* Register the I18n module
|
||||||
|
*/
|
||||||
|
static forRoot(options?: I18nOptions): DynamicModule {
|
||||||
|
return {
|
||||||
|
module: I18nModule,
|
||||||
|
imports: [ConfigModule, SessionModule.forRoot()],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: I18N_OPTIONS,
|
||||||
|
useValue: options || {},
|
||||||
|
},
|
||||||
|
I18nService,
|
||||||
|
],
|
||||||
|
exports: [I18nService],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the I18n module with async configuration
|
||||||
|
*/
|
||||||
|
static forRootAsync(options: {
|
||||||
|
imports?: any[];
|
||||||
|
useFactory: (...args: any[]) => I18nOptions | Promise<I18nOptions>;
|
||||||
|
inject?: any[];
|
||||||
|
}): DynamicModule {
|
||||||
|
return {
|
||||||
|
module: I18nModule,
|
||||||
|
imports: [...(options.imports || []), ConfigModule, SessionModule.forRoot()],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: I18N_OPTIONS,
|
||||||
|
useFactory: options.useFactory,
|
||||||
|
inject: options.inject || [],
|
||||||
|
},
|
||||||
|
I18nService,
|
||||||
|
],
|
||||||
|
exports: [I18nService],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
242
packages/bot-services/src/i18n/i18n.service.ts
Normal file
242
packages/bot-services/src/i18n/i18n.service.ts
Normal file
|
|
@ -0,0 +1,242 @@
|
||||||
|
import { Injectable, Inject, Optional, Logger } from '@nestjs/common';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import {
|
||||||
|
Language,
|
||||||
|
BotTranslations,
|
||||||
|
TodoTranslations,
|
||||||
|
CalendarTranslations,
|
||||||
|
ContactsTranslations,
|
||||||
|
ClockTranslations,
|
||||||
|
I18nOptions,
|
||||||
|
} from './types';
|
||||||
|
import { de } from './locales/de';
|
||||||
|
import { en } from './locales/en';
|
||||||
|
import { SessionService } from '../session/session.service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection token for I18n options
|
||||||
|
*/
|
||||||
|
export const I18N_OPTIONS = 'I18N_OPTIONS';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Session data key for language preference
|
||||||
|
*/
|
||||||
|
const LANGUAGE_KEY = 'language';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All available translations
|
||||||
|
*/
|
||||||
|
const translations: Record<Language, BotTranslations> = { de, en };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Language display names
|
||||||
|
*/
|
||||||
|
export const LANGUAGE_NAMES: Record<Language, string> = {
|
||||||
|
de: 'Deutsch',
|
||||||
|
en: 'English',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* I18n Service for Matrix Bots
|
||||||
|
*
|
||||||
|
* Provides multi-language support with:
|
||||||
|
* - Per-user language preference (stored in session)
|
||||||
|
* - Default language from environment variable
|
||||||
|
* - Placeholder substitution in translations
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* // Get translator for a user
|
||||||
|
* const t = await i18n.getTranslator(userId, 'todo');
|
||||||
|
*
|
||||||
|
* // Use translations
|
||||||
|
* const msg = t('taskCreated', { title: 'Buy milk' });
|
||||||
|
* // → "Aufgabe erstellt: **Buy milk**" (if user language is German)
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class I18nService {
|
||||||
|
private readonly logger = new Logger(I18nService.name);
|
||||||
|
private readonly defaultLanguage: Language;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Optional() private sessionService?: SessionService,
|
||||||
|
@Optional() private configService?: ConfigService,
|
||||||
|
@Optional() @Inject(I18N_OPTIONS) private options?: I18nOptions
|
||||||
|
) {
|
||||||
|
// Priority: options > env > config > 'de'
|
||||||
|
this.defaultLanguage =
|
||||||
|
options?.defaultLanguage ||
|
||||||
|
(process.env.BOT_DEFAULT_LANGUAGE as Language) ||
|
||||||
|
this.configService?.get<Language>('bot.defaultLanguage') ||
|
||||||
|
'de';
|
||||||
|
|
||||||
|
this.logger.log(`Default language: ${this.defaultLanguage}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the language for a user
|
||||||
|
*/
|
||||||
|
async getLanguage(userId: string): Promise<Language> {
|
||||||
|
if (this.sessionService) {
|
||||||
|
const lang = await this.sessionService.getSessionData<Language>(userId, LANGUAGE_KEY);
|
||||||
|
if (lang && this.isValidLanguage(lang)) {
|
||||||
|
return lang;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.defaultLanguage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the language for a user
|
||||||
|
*/
|
||||||
|
async setLanguage(userId: string, language: Language): Promise<void> {
|
||||||
|
if (!this.isValidLanguage(language)) {
|
||||||
|
throw new Error(
|
||||||
|
`Invalid language: ${language}. Available: ${this.getAvailableLanguages().join(', ')}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (this.sessionService) {
|
||||||
|
await this.sessionService.setSessionData(userId, LANGUAGE_KEY, language);
|
||||||
|
this.logger.log(`Language set for ${userId}: ${language}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a language code is valid
|
||||||
|
*/
|
||||||
|
isValidLanguage(lang: string): lang is Language {
|
||||||
|
return lang === 'de' || lang === 'en';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get list of available languages
|
||||||
|
*/
|
||||||
|
getAvailableLanguages(): Language[] {
|
||||||
|
return ['de', 'en'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get language display name
|
||||||
|
*/
|
||||||
|
getLanguageName(lang: Language): string {
|
||||||
|
return LANGUAGE_NAMES[lang];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all translations for a language
|
||||||
|
*/
|
||||||
|
getTranslations(language: Language): BotTranslations {
|
||||||
|
return translations[language] || translations[this.defaultLanguage];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a translator function for todo bot
|
||||||
|
*/
|
||||||
|
async getTodoTranslator(
|
||||||
|
userId: string
|
||||||
|
): Promise<(key: keyof TodoTranslations, params?: Record<string, string | number>) => string> {
|
||||||
|
const lang = await this.getLanguage(userId);
|
||||||
|
const t = translations[lang].todo;
|
||||||
|
return (key, params) => this.interpolate(t[key], params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a translator function for calendar bot
|
||||||
|
*/
|
||||||
|
async getCalendarTranslator(
|
||||||
|
userId: string
|
||||||
|
): Promise<
|
||||||
|
(key: keyof CalendarTranslations, params?: Record<string, string | number>) => string
|
||||||
|
> {
|
||||||
|
const lang = await this.getLanguage(userId);
|
||||||
|
const t = translations[lang].calendar;
|
||||||
|
return (key, params) => this.interpolate(t[key], params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a translator function for contacts bot
|
||||||
|
*/
|
||||||
|
async getContactsTranslator(
|
||||||
|
userId: string
|
||||||
|
): Promise<
|
||||||
|
(key: keyof ContactsTranslations, params?: Record<string, string | number>) => string
|
||||||
|
> {
|
||||||
|
const lang = await this.getLanguage(userId);
|
||||||
|
const t = translations[lang].contacts;
|
||||||
|
return (key, params) => this.interpolate(t[key], params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a translator function for clock bot
|
||||||
|
*/
|
||||||
|
async getClockTranslator(
|
||||||
|
userId: string
|
||||||
|
): Promise<(key: keyof ClockTranslations, params?: Record<string, string | number>) => string> {
|
||||||
|
const lang = await this.getLanguage(userId);
|
||||||
|
const t = translations[lang].clock;
|
||||||
|
return (key, params) => this.interpolate(t[key], params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get translations directly for a bot type
|
||||||
|
*/
|
||||||
|
async getTodoTranslations(userId: string): Promise<TodoTranslations> {
|
||||||
|
const lang = await this.getLanguage(userId);
|
||||||
|
return translations[lang].todo;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCalendarTranslations(userId: string): Promise<CalendarTranslations> {
|
||||||
|
const lang = await this.getLanguage(userId);
|
||||||
|
return translations[lang].calendar;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getContactsTranslations(userId: string): Promise<ContactsTranslations> {
|
||||||
|
const lang = await this.getLanguage(userId);
|
||||||
|
return translations[lang].contacts;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getClockTranslations(userId: string): Promise<ClockTranslations> {
|
||||||
|
const lang = await this.getLanguage(userId);
|
||||||
|
return translations[lang].clock;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interpolate placeholders in a string
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* interpolate('Hello {name}!', { name: 'World' })
|
||||||
|
* // → 'Hello World!'
|
||||||
|
*/
|
||||||
|
interpolate(template: string, params?: Record<string, string | number>): string {
|
||||||
|
if (!params) return template;
|
||||||
|
return template.replace(/\{(\w+)\}/g, (_, key) => {
|
||||||
|
return params[key]?.toString() ?? `{${key}}`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a date according to user's language
|
||||||
|
*/
|
||||||
|
async formatDate(userId: string, date: Date | string): Promise<string> {
|
||||||
|
const lang = await this.getLanguage(userId);
|
||||||
|
const d = typeof date === 'string' ? new Date(date) : date;
|
||||||
|
return d.toLocaleDateString(lang === 'de' ? 'de-DE' : 'en-US', {
|
||||||
|
day: '2-digit',
|
||||||
|
month: '2-digit',
|
||||||
|
year: 'numeric',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a time according to user's language
|
||||||
|
*/
|
||||||
|
async formatTime(userId: string, date: Date | string): Promise<string> {
|
||||||
|
const lang = await this.getLanguage(userId);
|
||||||
|
const d = typeof date === 'string' ? new Date(date) : date;
|
||||||
|
return d.toLocaleTimeString(lang === 'de' ? 'de-DE' : 'en-US', {
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
5
packages/bot-services/src/i18n/index.ts
Normal file
5
packages/bot-services/src/i18n/index.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
export * from './types';
|
||||||
|
export * from './i18n.service';
|
||||||
|
export * from './i18n.module';
|
||||||
|
export { de } from './locales/de';
|
||||||
|
export { en } from './locales/en';
|
||||||
390
packages/bot-services/src/i18n/locales/de.ts
Normal file
390
packages/bot-services/src/i18n/locales/de.ts
Normal file
|
|
@ -0,0 +1,390 @@
|
||||||
|
import { type BotTranslations } from '../types';
|
||||||
|
|
||||||
|
export const de: BotTranslations = {
|
||||||
|
common: {
|
||||||
|
// General
|
||||||
|
error: 'Fehler',
|
||||||
|
errorOccurred: 'Ein Fehler ist aufgetreten. Bitte versuche es erneut.',
|
||||||
|
notLoggedIn: 'Du bist nicht angemeldet.',
|
||||||
|
loginRequired: 'Bitte melde dich zuerst an mit `!login email passwort`',
|
||||||
|
loginSuccess: 'Erfolgreich angemeldet als **{email}**',
|
||||||
|
loginFailed: 'Anmeldung fehlgeschlagen: {error}',
|
||||||
|
logoutSuccess: 'Erfolgreich abgemeldet.',
|
||||||
|
invalidCommand: 'Unbekannter Befehl: {command}',
|
||||||
|
helpHint: 'Sag "hilfe" für alle Befehle.',
|
||||||
|
|
||||||
|
// Credits
|
||||||
|
credits: 'Credits',
|
||||||
|
creditsRemaining: '{amount} verbleibend',
|
||||||
|
insufficientCredits: 'Nicht genügend Credits. Benötigt: {required}, Verfügbar: {available}',
|
||||||
|
buyCredits: 'Credits kaufen: https://mana.how/credits',
|
||||||
|
|
||||||
|
// Sync
|
||||||
|
synced: 'Synchronisiert',
|
||||||
|
localStorage: 'Lokaler Speicher',
|
||||||
|
|
||||||
|
// Status
|
||||||
|
status: 'Status',
|
||||||
|
online: 'Online',
|
||||||
|
offline: 'Offline',
|
||||||
|
loggedInAs: 'Angemeldet als: {email}',
|
||||||
|
notLoggedInStatus: 'Nicht angemeldet',
|
||||||
|
|
||||||
|
// Language
|
||||||
|
languageChanged: 'Sprache geändert zu: **{language}**',
|
||||||
|
currentLanguage: 'Aktuelle Sprache: **{language}**',
|
||||||
|
availableLanguages: 'Verfügbare Sprachen: {languages}',
|
||||||
|
|
||||||
|
// Dates
|
||||||
|
today: 'Heute',
|
||||||
|
tomorrow: 'Morgen',
|
||||||
|
dayAfterTomorrow: 'Übermorgen',
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
created: 'Erstellt',
|
||||||
|
deleted: 'Gelöscht',
|
||||||
|
updated: 'Aktualisiert',
|
||||||
|
completed: 'Erledigt',
|
||||||
|
},
|
||||||
|
|
||||||
|
todo: {
|
||||||
|
// Inherit common
|
||||||
|
error: 'Fehler',
|
||||||
|
errorOccurred: 'Ein Fehler ist aufgetreten. Bitte versuche es erneut.',
|
||||||
|
notLoggedIn: 'Du bist nicht angemeldet.',
|
||||||
|
loginRequired: 'Bitte melde dich zuerst an mit `!login email passwort`',
|
||||||
|
loginSuccess: 'Erfolgreich angemeldet als **{email}**',
|
||||||
|
loginFailed: 'Anmeldung fehlgeschlagen: {error}',
|
||||||
|
logoutSuccess: 'Erfolgreich abgemeldet.',
|
||||||
|
invalidCommand: 'Unbekannter Befehl: {command}',
|
||||||
|
helpHint: 'Sag "hilfe" für alle Befehle.',
|
||||||
|
credits: 'Credits',
|
||||||
|
creditsRemaining: '{amount} verbleibend',
|
||||||
|
insufficientCredits: 'Nicht genügend Credits. Benötigt: {required}, Verfügbar: {available}',
|
||||||
|
buyCredits: 'Credits kaufen: https://mana.how/credits',
|
||||||
|
synced: 'Synchronisiert',
|
||||||
|
localStorage: 'Lokaler Speicher',
|
||||||
|
status: 'Status',
|
||||||
|
online: 'Online',
|
||||||
|
offline: 'Offline',
|
||||||
|
loggedInAs: 'Angemeldet als: {email}',
|
||||||
|
notLoggedInStatus: 'Nicht angemeldet',
|
||||||
|
languageChanged: 'Sprache geändert zu: **{language}**',
|
||||||
|
currentLanguage: 'Aktuelle Sprache: **{language}**',
|
||||||
|
availableLanguages: 'Verfügbare Sprachen: {languages}',
|
||||||
|
today: 'Heute',
|
||||||
|
tomorrow: 'Morgen',
|
||||||
|
dayAfterTomorrow: 'Übermorgen',
|
||||||
|
created: 'Erstellt',
|
||||||
|
deleted: 'Gelöscht',
|
||||||
|
updated: 'Aktualisiert',
|
||||||
|
completed: 'Erledigt',
|
||||||
|
|
||||||
|
// Tasks
|
||||||
|
task: 'Aufgabe',
|
||||||
|
tasks: 'Aufgaben',
|
||||||
|
taskCreated: 'Aufgabe erstellt: **{title}**',
|
||||||
|
taskCompleted: 'Erledigt: ~~{title}~~',
|
||||||
|
taskDeleted: 'Gelöscht: {title}',
|
||||||
|
noTasks: 'Keine offenen Aufgaben.',
|
||||||
|
noTasksToday: 'Keine Aufgaben für heute.',
|
||||||
|
inboxEmpty: 'Inbox ist leer.',
|
||||||
|
allTasks: 'Alle offenen Aufgaben',
|
||||||
|
todayTasks: 'Aufgaben für heute',
|
||||||
|
inbox: 'Inbox (ohne Datum)',
|
||||||
|
|
||||||
|
// Projects
|
||||||
|
project: 'Projekt',
|
||||||
|
projects: 'Projekte',
|
||||||
|
noProjects: 'Keine Projekte.',
|
||||||
|
projectTasks: 'Projekt: {name}',
|
||||||
|
|
||||||
|
// Priorities
|
||||||
|
priority: 'Priorität',
|
||||||
|
date: 'Datum',
|
||||||
|
|
||||||
|
// Help
|
||||||
|
helpTitle: 'Todo Bot - Hilfe',
|
||||||
|
helpCommands: `**Befehle:**
|
||||||
|
• \`!add [Aufgabe]\` - Neue Aufgabe erstellen
|
||||||
|
• \`!list\` - Alle offenen Aufgaben
|
||||||
|
• \`!today\` - Heutige Aufgaben
|
||||||
|
• \`!inbox\` - Aufgaben ohne Datum
|
||||||
|
• \`!done [Nr]\` - Aufgabe als erledigt markieren
|
||||||
|
• \`!delete [Nr]\` - Aufgabe löschen
|
||||||
|
• \`!projects\` - Alle Projekte
|
||||||
|
• \`!project [Name]\` - Projektaufgaben anzeigen
|
||||||
|
• \`!status\` - Bot-Status
|
||||||
|
• \`!language [de/en]\` - Sprache ändern`,
|
||||||
|
helpSyntax: `**Syntax:**
|
||||||
|
\`!add Aufgabe !p1 @morgen #projekt\`
|
||||||
|
• \`!p1-4\` - Priorität (1=höchste)
|
||||||
|
• \`@heute/@morgen/@übermorgen\` - Datum
|
||||||
|
• \`#projektname\` - Projekt`,
|
||||||
|
helpExamples: `**Beispiele:**
|
||||||
|
• \`Einkaufen gehen\`
|
||||||
|
• \`Meeting vorbereiten !p1 @morgen\`
|
||||||
|
• \`Bericht schreiben #arbeit\``,
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
markDone: 'Erledigen: `!done [Nr]`',
|
||||||
|
delete: 'Löschen: `!delete [Nr]`',
|
||||||
|
},
|
||||||
|
|
||||||
|
calendar: {
|
||||||
|
// Inherit common
|
||||||
|
error: 'Fehler',
|
||||||
|
errorOccurred: 'Ein Fehler ist aufgetreten. Bitte versuche es erneut.',
|
||||||
|
notLoggedIn: 'Du bist nicht angemeldet.',
|
||||||
|
loginRequired: 'Bitte melde dich zuerst an mit `!login email passwort`',
|
||||||
|
loginSuccess: 'Erfolgreich angemeldet als **{email}**',
|
||||||
|
loginFailed: 'Anmeldung fehlgeschlagen: {error}',
|
||||||
|
logoutSuccess: 'Erfolgreich abgemeldet.',
|
||||||
|
invalidCommand: 'Unbekannter Befehl: {command}',
|
||||||
|
helpHint: 'Sag "hilfe" für alle Befehle.',
|
||||||
|
credits: 'Credits',
|
||||||
|
creditsRemaining: '{amount} verbleibend',
|
||||||
|
insufficientCredits: 'Nicht genügend Credits. Benötigt: {required}, Verfügbar: {available}',
|
||||||
|
buyCredits: 'Credits kaufen: https://mana.how/credits',
|
||||||
|
synced: 'Synchronisiert',
|
||||||
|
localStorage: 'Lokaler Speicher',
|
||||||
|
status: 'Status',
|
||||||
|
online: 'Online',
|
||||||
|
offline: 'Offline',
|
||||||
|
loggedInAs: 'Angemeldet als: {email}',
|
||||||
|
notLoggedInStatus: 'Nicht angemeldet',
|
||||||
|
languageChanged: 'Sprache geändert zu: **{language}**',
|
||||||
|
currentLanguage: 'Aktuelle Sprache: **{language}**',
|
||||||
|
availableLanguages: 'Verfügbare Sprachen: {languages}',
|
||||||
|
today: 'Heute',
|
||||||
|
tomorrow: 'Morgen',
|
||||||
|
dayAfterTomorrow: 'Übermorgen',
|
||||||
|
created: 'Erstellt',
|
||||||
|
deleted: 'Gelöscht',
|
||||||
|
updated: 'Aktualisiert',
|
||||||
|
completed: 'Erledigt',
|
||||||
|
|
||||||
|
// Events
|
||||||
|
event: 'Termin',
|
||||||
|
events: 'Termine',
|
||||||
|
eventCreated: 'Termin erstellt: **{title}**',
|
||||||
|
eventDeleted: 'Gelöscht: {title}',
|
||||||
|
noEvents: 'Keine anstehenden Termine.',
|
||||||
|
noEventsToday: 'Keine Termine für heute.',
|
||||||
|
noEventsTomorrow: 'Keine Termine für morgen.',
|
||||||
|
noEventsThisWeek: 'Keine Termine diese Woche.',
|
||||||
|
upcomingEvents: 'Anstehende Termine',
|
||||||
|
todayEvents: 'Termine heute',
|
||||||
|
tomorrowEvents: 'Termine morgen',
|
||||||
|
weekEvents: 'Termine diese Woche',
|
||||||
|
|
||||||
|
// Calendars
|
||||||
|
calendar: 'Kalender',
|
||||||
|
calendars: 'Kalender',
|
||||||
|
yourCalendars: 'Deine Kalender',
|
||||||
|
|
||||||
|
// Time
|
||||||
|
time: 'Zeit',
|
||||||
|
allDay: 'ganztägig',
|
||||||
|
location: 'Ort',
|
||||||
|
|
||||||
|
// Help
|
||||||
|
helpTitle: 'Kalender Bot - Hilfe',
|
||||||
|
helpCommands: `**Befehle:**
|
||||||
|
• \`!add [Termin]\` - Neuen Termin erstellen
|
||||||
|
• \`!today\` - Heutige Termine
|
||||||
|
• \`!tomorrow\` - Morgige Termine
|
||||||
|
• \`!week\` - Termine diese Woche
|
||||||
|
• \`!events\` - Nächste 14 Tage
|
||||||
|
• \`!details [Nr]\` - Termindetails
|
||||||
|
• \`!delete [Nr]\` - Termin löschen
|
||||||
|
• \`!calendars\` - Alle Kalender
|
||||||
|
• \`!status\` - Bot-Status
|
||||||
|
• \`!language [de/en]\` - Sprache ändern`,
|
||||||
|
helpSyntax: `**Syntax:**
|
||||||
|
\`Meeting morgen um 14:00\`
|
||||||
|
\`Zahnarzt am 15.02. um 10:30\`
|
||||||
|
\`Urlaub am 01.03. ganztägig\``,
|
||||||
|
helpExamples: `**Beispiele:**
|
||||||
|
• \`Team Meeting morgen um 10:00\`
|
||||||
|
• \`Arzt am 20.02. um 15:30\`
|
||||||
|
• \`Geburtstag am 15.03. ganztägig\``,
|
||||||
|
|
||||||
|
// Parsing errors
|
||||||
|
couldNotParseDateTime: 'Konnte Datum/Uhrzeit nicht erkennen.',
|
||||||
|
pleaseProvideTitle: 'Bitte gib einen Titel für den Termin an.',
|
||||||
|
},
|
||||||
|
|
||||||
|
contacts: {
|
||||||
|
// Inherit common
|
||||||
|
error: 'Fehler',
|
||||||
|
errorOccurred: 'Ein Fehler ist aufgetreten. Bitte versuche es erneut.',
|
||||||
|
notLoggedIn: 'Du bist nicht angemeldet.',
|
||||||
|
loginRequired: 'Bitte melde dich zuerst an mit `!login email passwort`',
|
||||||
|
loginSuccess: 'Erfolgreich angemeldet als **{email}**',
|
||||||
|
loginFailed: 'Anmeldung fehlgeschlagen: {error}',
|
||||||
|
logoutSuccess: 'Erfolgreich abgemeldet.',
|
||||||
|
invalidCommand: 'Unbekannter Befehl: {command}',
|
||||||
|
helpHint: 'Sag "hilfe" für alle Befehle.',
|
||||||
|
credits: 'Credits',
|
||||||
|
creditsRemaining: '{amount} verbleibend',
|
||||||
|
insufficientCredits: 'Nicht genügend Credits. Benötigt: {required}, Verfügbar: {available}',
|
||||||
|
buyCredits: 'Credits kaufen: https://mana.how/credits',
|
||||||
|
synced: 'Synchronisiert',
|
||||||
|
localStorage: 'Lokaler Speicher',
|
||||||
|
status: 'Status',
|
||||||
|
online: 'Online',
|
||||||
|
offline: 'Offline',
|
||||||
|
loggedInAs: 'Angemeldet als: {email}',
|
||||||
|
notLoggedInStatus: 'Nicht angemeldet',
|
||||||
|
languageChanged: 'Sprache geändert zu: **{language}**',
|
||||||
|
currentLanguage: 'Aktuelle Sprache: **{language}**',
|
||||||
|
availableLanguages: 'Verfügbare Sprachen: {languages}',
|
||||||
|
today: 'Heute',
|
||||||
|
tomorrow: 'Morgen',
|
||||||
|
dayAfterTomorrow: 'Übermorgen',
|
||||||
|
created: 'Erstellt',
|
||||||
|
deleted: 'Gelöscht',
|
||||||
|
updated: 'Aktualisiert',
|
||||||
|
completed: 'Erledigt',
|
||||||
|
|
||||||
|
// Contacts
|
||||||
|
contact: 'Kontakt',
|
||||||
|
contacts: 'Kontakte',
|
||||||
|
contactCreated: 'Kontakt **{name}** erstellt!',
|
||||||
|
contactDeleted: 'Kontakt **{name}** gelöscht.',
|
||||||
|
contactUpdated: 'Kontakt **{name}** aktualisiert!',
|
||||||
|
noContacts: 'Du hast noch keine Kontakte.',
|
||||||
|
|
||||||
|
// Favorites
|
||||||
|
favorite: 'Favorit',
|
||||||
|
favorites: 'Favoriten',
|
||||||
|
noFavorites: 'Du hast noch keine Favoriten.',
|
||||||
|
markedAsFavorite: '**{name}** als Favorit markiert ★',
|
||||||
|
removedFromFavorites: '**{name}** aus Favoriten entfernt',
|
||||||
|
|
||||||
|
// Search
|
||||||
|
search: 'Suche',
|
||||||
|
searchResults: 'Suchergebnisse für "{query}"',
|
||||||
|
noSearchResults: 'Keine Kontakte gefunden für: "{query}"',
|
||||||
|
|
||||||
|
// Fields
|
||||||
|
email: 'E-Mail',
|
||||||
|
phone: 'Telefon',
|
||||||
|
mobile: 'Mobil',
|
||||||
|
company: 'Firma',
|
||||||
|
jobTitle: 'Beruf',
|
||||||
|
address: 'Adresse',
|
||||||
|
website: 'Website',
|
||||||
|
birthday: 'Geburtstag',
|
||||||
|
notes: 'Notizen',
|
||||||
|
|
||||||
|
// Help
|
||||||
|
helpTitle: 'Contacts Bot - Hilfe',
|
||||||
|
helpCommands: `**Befehle:**
|
||||||
|
• \`!contacts\` - Alle Kontakte
|
||||||
|
• \`!search [text]\` - Kontakte suchen
|
||||||
|
• \`!favorites\` - Favoriten anzeigen
|
||||||
|
• \`!contact [Nr]\` - Kontaktdetails
|
||||||
|
• \`!add Vorname Nachname\` - Neuer Kontakt
|
||||||
|
• \`!edit [Nr] [feld] [wert]\` - Bearbeiten
|
||||||
|
• \`!delete [Nr]\` - Kontakt löschen
|
||||||
|
• \`!fav [Nr]\` - Favorit umschalten
|
||||||
|
• \`!status\` - Bot-Status
|
||||||
|
• \`!language [de/en]\` - Sprache ändern`,
|
||||||
|
helpFields: `**Felder:** email, phone, mobile, company, job, website, street, city, zip, country, notes, birthday`,
|
||||||
|
helpExamples: `**Beispiele:**
|
||||||
|
• \`Max Mustermann\`
|
||||||
|
• \`!edit 1 email max@example.com\`
|
||||||
|
• \`!edit 1 phone +49 123 456789\``,
|
||||||
|
},
|
||||||
|
|
||||||
|
clock: {
|
||||||
|
// Inherit common
|
||||||
|
error: 'Fehler',
|
||||||
|
errorOccurred: 'Ein Fehler ist aufgetreten. Bitte versuche es erneut.',
|
||||||
|
notLoggedIn: 'Du bist nicht angemeldet.',
|
||||||
|
loginRequired: 'Bitte melde dich zuerst an mit `!login email passwort`',
|
||||||
|
loginSuccess: 'Erfolgreich angemeldet als **{email}**',
|
||||||
|
loginFailed: 'Anmeldung fehlgeschlagen: {error}',
|
||||||
|
logoutSuccess: 'Erfolgreich abgemeldet.',
|
||||||
|
invalidCommand: 'Unbekannter Befehl: {command}',
|
||||||
|
helpHint: 'Sag "hilfe" für alle Befehle.',
|
||||||
|
credits: 'Credits',
|
||||||
|
creditsRemaining: '{amount} verbleibend',
|
||||||
|
insufficientCredits: 'Nicht genügend Credits. Benötigt: {required}, Verfügbar: {available}',
|
||||||
|
buyCredits: 'Credits kaufen: https://mana.how/credits',
|
||||||
|
synced: 'Synchronisiert',
|
||||||
|
localStorage: 'Lokaler Speicher',
|
||||||
|
status: 'Status',
|
||||||
|
online: 'Online',
|
||||||
|
offline: 'Offline',
|
||||||
|
loggedInAs: 'Angemeldet als: {email}',
|
||||||
|
notLoggedInStatus: 'Nicht angemeldet',
|
||||||
|
languageChanged: 'Sprache geändert zu: **{language}**',
|
||||||
|
currentLanguage: 'Aktuelle Sprache: **{language}**',
|
||||||
|
availableLanguages: 'Verfügbare Sprachen: {languages}',
|
||||||
|
today: 'Heute',
|
||||||
|
tomorrow: 'Morgen',
|
||||||
|
dayAfterTomorrow: 'Übermorgen',
|
||||||
|
created: 'Erstellt',
|
||||||
|
deleted: 'Gelöscht',
|
||||||
|
updated: 'Aktualisiert',
|
||||||
|
completed: 'Erledigt',
|
||||||
|
|
||||||
|
// Timer
|
||||||
|
timer: 'Timer',
|
||||||
|
timerStarted: 'Timer gestartet!',
|
||||||
|
timerPaused: 'Timer pausiert',
|
||||||
|
timerResumed: 'Timer fortgesetzt',
|
||||||
|
timerReset: 'Timer zurückgesetzt.',
|
||||||
|
timerFinished: 'Timer beendet!',
|
||||||
|
noActiveTimer: 'Kein aktiver Timer.',
|
||||||
|
noPausedTimer: 'Kein pausierter Timer.',
|
||||||
|
noTimers: 'Keine Timer.',
|
||||||
|
remaining: 'Verbleibend',
|
||||||
|
duration: 'Dauer',
|
||||||
|
label: 'Label',
|
||||||
|
|
||||||
|
// Alarm
|
||||||
|
alarm: 'Alarm',
|
||||||
|
alarmSet: 'Alarm gestellt!',
|
||||||
|
alarmDeleted: 'Alarm gelöscht.',
|
||||||
|
noAlarms: 'Keine Alarme.',
|
||||||
|
yourAlarms: 'Deine Alarme',
|
||||||
|
|
||||||
|
// World Clock
|
||||||
|
worldClock: 'Weltuhr',
|
||||||
|
worldClocks: 'Weltuhren',
|
||||||
|
worldClockAdded: 'Weltuhr hinzugefügt: {city}',
|
||||||
|
noWorldClocks: 'Keine Weltuhren.',
|
||||||
|
yourWorldClocks: 'Deine Weltuhren',
|
||||||
|
|
||||||
|
// Time
|
||||||
|
currentTime: 'Aktuelle Zeit',
|
||||||
|
|
||||||
|
// Help
|
||||||
|
helpTitle: 'Clock Bot - Hilfe',
|
||||||
|
helpCommands: `**Befehle:**
|
||||||
|
• \`!timer 25m\` - Timer starten
|
||||||
|
• \`!stop\` - Timer pausieren
|
||||||
|
• \`!resume\` - Timer fortsetzen
|
||||||
|
• \`!reset\` - Timer zurücksetzen
|
||||||
|
• \`!timers\` - Alle Timer
|
||||||
|
• \`!alarm 07:30\` - Alarm stellen
|
||||||
|
• \`!alarms\` - Alle Alarme
|
||||||
|
• \`!time\` - Aktuelle Zeit
|
||||||
|
• \`!worldclock Berlin\` - Weltuhr hinzufügen
|
||||||
|
• \`!worldclocks\` - Alle Weltuhren
|
||||||
|
• \`!status\` - Bot-Status
|
||||||
|
• \`!language [de/en]\` - Sprache ändern`,
|
||||||
|
helpExamples: `**Beispiele:**
|
||||||
|
• \`25\` (25 Minuten Timer)
|
||||||
|
• \`1h30m\` (1,5 Stunden Timer)
|
||||||
|
• \`!alarm 7 Uhr 30\``,
|
||||||
|
|
||||||
|
// Parsing errors
|
||||||
|
couldNotParseDuration: 'Konnte Zeit nicht verstehen.',
|
||||||
|
couldNotParseTime: 'Konnte Uhrzeit nicht verstehen.',
|
||||||
|
},
|
||||||
|
};
|
||||||
390
packages/bot-services/src/i18n/locales/en.ts
Normal file
390
packages/bot-services/src/i18n/locales/en.ts
Normal file
|
|
@ -0,0 +1,390 @@
|
||||||
|
import { type BotTranslations } from '../types';
|
||||||
|
|
||||||
|
export const en: BotTranslations = {
|
||||||
|
common: {
|
||||||
|
// General
|
||||||
|
error: 'Error',
|
||||||
|
errorOccurred: 'An error occurred. Please try again.',
|
||||||
|
notLoggedIn: 'You are not logged in.',
|
||||||
|
loginRequired: 'Please log in first with `!login email password`',
|
||||||
|
loginSuccess: 'Successfully logged in as **{email}**',
|
||||||
|
loginFailed: 'Login failed: {error}',
|
||||||
|
logoutSuccess: 'Successfully logged out.',
|
||||||
|
invalidCommand: 'Unknown command: {command}',
|
||||||
|
helpHint: 'Say "help" for all commands.',
|
||||||
|
|
||||||
|
// Credits
|
||||||
|
credits: 'Credits',
|
||||||
|
creditsRemaining: '{amount} remaining',
|
||||||
|
insufficientCredits: 'Insufficient credits. Required: {required}, Available: {available}',
|
||||||
|
buyCredits: 'Buy credits: https://mana.how/credits',
|
||||||
|
|
||||||
|
// Sync
|
||||||
|
synced: 'Synced',
|
||||||
|
localStorage: 'Local storage',
|
||||||
|
|
||||||
|
// Status
|
||||||
|
status: 'Status',
|
||||||
|
online: 'Online',
|
||||||
|
offline: 'Offline',
|
||||||
|
loggedInAs: 'Logged in as: {email}',
|
||||||
|
notLoggedInStatus: 'Not logged in',
|
||||||
|
|
||||||
|
// Language
|
||||||
|
languageChanged: 'Language changed to: **{language}**',
|
||||||
|
currentLanguage: 'Current language: **{language}**',
|
||||||
|
availableLanguages: 'Available languages: {languages}',
|
||||||
|
|
||||||
|
// Dates
|
||||||
|
today: 'Today',
|
||||||
|
tomorrow: 'Tomorrow',
|
||||||
|
dayAfterTomorrow: 'Day after tomorrow',
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
created: 'Created',
|
||||||
|
deleted: 'Deleted',
|
||||||
|
updated: 'Updated',
|
||||||
|
completed: 'Completed',
|
||||||
|
},
|
||||||
|
|
||||||
|
todo: {
|
||||||
|
// Inherit common
|
||||||
|
error: 'Error',
|
||||||
|
errorOccurred: 'An error occurred. Please try again.',
|
||||||
|
notLoggedIn: 'You are not logged in.',
|
||||||
|
loginRequired: 'Please log in first with `!login email password`',
|
||||||
|
loginSuccess: 'Successfully logged in as **{email}**',
|
||||||
|
loginFailed: 'Login failed: {error}',
|
||||||
|
logoutSuccess: 'Successfully logged out.',
|
||||||
|
invalidCommand: 'Unknown command: {command}',
|
||||||
|
helpHint: 'Say "help" for all commands.',
|
||||||
|
credits: 'Credits',
|
||||||
|
creditsRemaining: '{amount} remaining',
|
||||||
|
insufficientCredits: 'Insufficient credits. Required: {required}, Available: {available}',
|
||||||
|
buyCredits: 'Buy credits: https://mana.how/credits',
|
||||||
|
synced: 'Synced',
|
||||||
|
localStorage: 'Local storage',
|
||||||
|
status: 'Status',
|
||||||
|
online: 'Online',
|
||||||
|
offline: 'Offline',
|
||||||
|
loggedInAs: 'Logged in as: {email}',
|
||||||
|
notLoggedInStatus: 'Not logged in',
|
||||||
|
languageChanged: 'Language changed to: **{language}**',
|
||||||
|
currentLanguage: 'Current language: **{language}**',
|
||||||
|
availableLanguages: 'Available languages: {languages}',
|
||||||
|
today: 'Today',
|
||||||
|
tomorrow: 'Tomorrow',
|
||||||
|
dayAfterTomorrow: 'Day after tomorrow',
|
||||||
|
created: 'Created',
|
||||||
|
deleted: 'Deleted',
|
||||||
|
updated: 'Updated',
|
||||||
|
completed: 'Completed',
|
||||||
|
|
||||||
|
// Tasks
|
||||||
|
task: 'Task',
|
||||||
|
tasks: 'Tasks',
|
||||||
|
taskCreated: 'Task created: **{title}**',
|
||||||
|
taskCompleted: 'Completed: ~~{title}~~',
|
||||||
|
taskDeleted: 'Deleted: {title}',
|
||||||
|
noTasks: 'No open tasks.',
|
||||||
|
noTasksToday: 'No tasks for today.',
|
||||||
|
inboxEmpty: 'Inbox is empty.',
|
||||||
|
allTasks: 'All open tasks',
|
||||||
|
todayTasks: 'Tasks for today',
|
||||||
|
inbox: 'Inbox (no date)',
|
||||||
|
|
||||||
|
// Projects
|
||||||
|
project: 'Project',
|
||||||
|
projects: 'Projects',
|
||||||
|
noProjects: 'No projects.',
|
||||||
|
projectTasks: 'Project: {name}',
|
||||||
|
|
||||||
|
// Priorities
|
||||||
|
priority: 'Priority',
|
||||||
|
date: 'Date',
|
||||||
|
|
||||||
|
// Help
|
||||||
|
helpTitle: 'Todo Bot - Help',
|
||||||
|
helpCommands: `**Commands:**
|
||||||
|
• \`!add [task]\` - Create new task
|
||||||
|
• \`!list\` - All open tasks
|
||||||
|
• \`!today\` - Today's tasks
|
||||||
|
• \`!inbox\` - Tasks without date
|
||||||
|
• \`!done [Nr]\` - Mark task as done
|
||||||
|
• \`!delete [Nr]\` - Delete task
|
||||||
|
• \`!projects\` - All projects
|
||||||
|
• \`!project [name]\` - Show project tasks
|
||||||
|
• \`!status\` - Bot status
|
||||||
|
• \`!language [de/en]\` - Change language`,
|
||||||
|
helpSyntax: `**Syntax:**
|
||||||
|
\`!add Task !p1 @tomorrow #project\`
|
||||||
|
• \`!p1-4\` - Priority (1=highest)
|
||||||
|
• \`@today/@tomorrow\` - Due date
|
||||||
|
• \`#projectname\` - Project`,
|
||||||
|
helpExamples: `**Examples:**
|
||||||
|
• \`Go shopping\`
|
||||||
|
• \`Prepare meeting !p1 @tomorrow\`
|
||||||
|
• \`Write report #work\``,
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
markDone: 'Complete: `!done [Nr]`',
|
||||||
|
delete: 'Delete: `!delete [Nr]`',
|
||||||
|
},
|
||||||
|
|
||||||
|
calendar: {
|
||||||
|
// Inherit common
|
||||||
|
error: 'Error',
|
||||||
|
errorOccurred: 'An error occurred. Please try again.',
|
||||||
|
notLoggedIn: 'You are not logged in.',
|
||||||
|
loginRequired: 'Please log in first with `!login email password`',
|
||||||
|
loginSuccess: 'Successfully logged in as **{email}**',
|
||||||
|
loginFailed: 'Login failed: {error}',
|
||||||
|
logoutSuccess: 'Successfully logged out.',
|
||||||
|
invalidCommand: 'Unknown command: {command}',
|
||||||
|
helpHint: 'Say "help" for all commands.',
|
||||||
|
credits: 'Credits',
|
||||||
|
creditsRemaining: '{amount} remaining',
|
||||||
|
insufficientCredits: 'Insufficient credits. Required: {required}, Available: {available}',
|
||||||
|
buyCredits: 'Buy credits: https://mana.how/credits',
|
||||||
|
synced: 'Synced',
|
||||||
|
localStorage: 'Local storage',
|
||||||
|
status: 'Status',
|
||||||
|
online: 'Online',
|
||||||
|
offline: 'Offline',
|
||||||
|
loggedInAs: 'Logged in as: {email}',
|
||||||
|
notLoggedInStatus: 'Not logged in',
|
||||||
|
languageChanged: 'Language changed to: **{language}**',
|
||||||
|
currentLanguage: 'Current language: **{language}**',
|
||||||
|
availableLanguages: 'Available languages: {languages}',
|
||||||
|
today: 'Today',
|
||||||
|
tomorrow: 'Tomorrow',
|
||||||
|
dayAfterTomorrow: 'Day after tomorrow',
|
||||||
|
created: 'Created',
|
||||||
|
deleted: 'Deleted',
|
||||||
|
updated: 'Updated',
|
||||||
|
completed: 'Completed',
|
||||||
|
|
||||||
|
// Events
|
||||||
|
event: 'Event',
|
||||||
|
events: 'Events',
|
||||||
|
eventCreated: 'Event created: **{title}**',
|
||||||
|
eventDeleted: 'Deleted: {title}',
|
||||||
|
noEvents: 'No upcoming events.',
|
||||||
|
noEventsToday: 'No events for today.',
|
||||||
|
noEventsTomorrow: 'No events for tomorrow.',
|
||||||
|
noEventsThisWeek: 'No events this week.',
|
||||||
|
upcomingEvents: 'Upcoming events',
|
||||||
|
todayEvents: "Today's events",
|
||||||
|
tomorrowEvents: "Tomorrow's events",
|
||||||
|
weekEvents: "This week's events",
|
||||||
|
|
||||||
|
// Calendars
|
||||||
|
calendar: 'Calendar',
|
||||||
|
calendars: 'Calendars',
|
||||||
|
yourCalendars: 'Your calendars',
|
||||||
|
|
||||||
|
// Time
|
||||||
|
time: 'Time',
|
||||||
|
allDay: 'all day',
|
||||||
|
location: 'Location',
|
||||||
|
|
||||||
|
// Help
|
||||||
|
helpTitle: 'Calendar Bot - Help',
|
||||||
|
helpCommands: `**Commands:**
|
||||||
|
• \`!add [event]\` - Create new event
|
||||||
|
• \`!today\` - Today's events
|
||||||
|
• \`!tomorrow\` - Tomorrow's events
|
||||||
|
• \`!week\` - This week's events
|
||||||
|
• \`!events\` - Next 14 days
|
||||||
|
• \`!details [Nr]\` - Event details
|
||||||
|
• \`!delete [Nr]\` - Delete event
|
||||||
|
• \`!calendars\` - All calendars
|
||||||
|
• \`!status\` - Bot status
|
||||||
|
• \`!language [de/en]\` - Change language`,
|
||||||
|
helpSyntax: `**Syntax:**
|
||||||
|
\`Meeting tomorrow at 2pm\`
|
||||||
|
\`Dentist on 02/15 at 10:30am\`
|
||||||
|
\`Vacation on 03/01 all day\``,
|
||||||
|
helpExamples: `**Examples:**
|
||||||
|
• \`Team meeting tomorrow at 10am\`
|
||||||
|
• \`Doctor on 02/20 at 3:30pm\`
|
||||||
|
• \`Birthday on 03/15 all day\``,
|
||||||
|
|
||||||
|
// Parsing errors
|
||||||
|
couldNotParseDateTime: 'Could not parse date/time.',
|
||||||
|
pleaseProvideTitle: 'Please provide a title for the event.',
|
||||||
|
},
|
||||||
|
|
||||||
|
contacts: {
|
||||||
|
// Inherit common
|
||||||
|
error: 'Error',
|
||||||
|
errorOccurred: 'An error occurred. Please try again.',
|
||||||
|
notLoggedIn: 'You are not logged in.',
|
||||||
|
loginRequired: 'Please log in first with `!login email password`',
|
||||||
|
loginSuccess: 'Successfully logged in as **{email}**',
|
||||||
|
loginFailed: 'Login failed: {error}',
|
||||||
|
logoutSuccess: 'Successfully logged out.',
|
||||||
|
invalidCommand: 'Unknown command: {command}',
|
||||||
|
helpHint: 'Say "help" for all commands.',
|
||||||
|
credits: 'Credits',
|
||||||
|
creditsRemaining: '{amount} remaining',
|
||||||
|
insufficientCredits: 'Insufficient credits. Required: {required}, Available: {available}',
|
||||||
|
buyCredits: 'Buy credits: https://mana.how/credits',
|
||||||
|
synced: 'Synced',
|
||||||
|
localStorage: 'Local storage',
|
||||||
|
status: 'Status',
|
||||||
|
online: 'Online',
|
||||||
|
offline: 'Offline',
|
||||||
|
loggedInAs: 'Logged in as: {email}',
|
||||||
|
notLoggedInStatus: 'Not logged in',
|
||||||
|
languageChanged: 'Language changed to: **{language}**',
|
||||||
|
currentLanguage: 'Current language: **{language}**',
|
||||||
|
availableLanguages: 'Available languages: {languages}',
|
||||||
|
today: 'Today',
|
||||||
|
tomorrow: 'Tomorrow',
|
||||||
|
dayAfterTomorrow: 'Day after tomorrow',
|
||||||
|
created: 'Created',
|
||||||
|
deleted: 'Deleted',
|
||||||
|
updated: 'Updated',
|
||||||
|
completed: 'Completed',
|
||||||
|
|
||||||
|
// Contacts
|
||||||
|
contact: 'Contact',
|
||||||
|
contacts: 'Contacts',
|
||||||
|
contactCreated: 'Contact **{name}** created!',
|
||||||
|
contactDeleted: 'Contact **{name}** deleted.',
|
||||||
|
contactUpdated: 'Contact **{name}** updated!',
|
||||||
|
noContacts: 'You have no contacts yet.',
|
||||||
|
|
||||||
|
// Favorites
|
||||||
|
favorite: 'Favorite',
|
||||||
|
favorites: 'Favorites',
|
||||||
|
noFavorites: 'You have no favorites yet.',
|
||||||
|
markedAsFavorite: '**{name}** marked as favorite ★',
|
||||||
|
removedFromFavorites: '**{name}** removed from favorites',
|
||||||
|
|
||||||
|
// Search
|
||||||
|
search: 'Search',
|
||||||
|
searchResults: 'Search results for "{query}"',
|
||||||
|
noSearchResults: 'No contacts found for: "{query}"',
|
||||||
|
|
||||||
|
// Fields
|
||||||
|
email: 'Email',
|
||||||
|
phone: 'Phone',
|
||||||
|
mobile: 'Mobile',
|
||||||
|
company: 'Company',
|
||||||
|
jobTitle: 'Job title',
|
||||||
|
address: 'Address',
|
||||||
|
website: 'Website',
|
||||||
|
birthday: 'Birthday',
|
||||||
|
notes: 'Notes',
|
||||||
|
|
||||||
|
// Help
|
||||||
|
helpTitle: 'Contacts Bot - Help',
|
||||||
|
helpCommands: `**Commands:**
|
||||||
|
• \`!contacts\` - All contacts
|
||||||
|
• \`!search [text]\` - Search contacts
|
||||||
|
• \`!favorites\` - Show favorites
|
||||||
|
• \`!contact [Nr]\` - Contact details
|
||||||
|
• \`!add FirstName LastName\` - New contact
|
||||||
|
• \`!edit [Nr] [field] [value]\` - Edit
|
||||||
|
• \`!delete [Nr]\` - Delete contact
|
||||||
|
• \`!fav [Nr]\` - Toggle favorite
|
||||||
|
• \`!status\` - Bot status
|
||||||
|
• \`!language [de/en]\` - Change language`,
|
||||||
|
helpFields: `**Fields:** email, phone, mobile, company, job, website, street, city, zip, country, notes, birthday`,
|
||||||
|
helpExamples: `**Examples:**
|
||||||
|
• \`John Doe\`
|
||||||
|
• \`!edit 1 email john@example.com\`
|
||||||
|
• \`!edit 1 phone +1 123 456 7890\``,
|
||||||
|
},
|
||||||
|
|
||||||
|
clock: {
|
||||||
|
// Inherit common
|
||||||
|
error: 'Error',
|
||||||
|
errorOccurred: 'An error occurred. Please try again.',
|
||||||
|
notLoggedIn: 'You are not logged in.',
|
||||||
|
loginRequired: 'Please log in first with `!login email password`',
|
||||||
|
loginSuccess: 'Successfully logged in as **{email}**',
|
||||||
|
loginFailed: 'Login failed: {error}',
|
||||||
|
logoutSuccess: 'Successfully logged out.',
|
||||||
|
invalidCommand: 'Unknown command: {command}',
|
||||||
|
helpHint: 'Say "help" for all commands.',
|
||||||
|
credits: 'Credits',
|
||||||
|
creditsRemaining: '{amount} remaining',
|
||||||
|
insufficientCredits: 'Insufficient credits. Required: {required}, Available: {available}',
|
||||||
|
buyCredits: 'Buy credits: https://mana.how/credits',
|
||||||
|
synced: 'Synced',
|
||||||
|
localStorage: 'Local storage',
|
||||||
|
status: 'Status',
|
||||||
|
online: 'Online',
|
||||||
|
offline: 'Offline',
|
||||||
|
loggedInAs: 'Logged in as: {email}',
|
||||||
|
notLoggedInStatus: 'Not logged in',
|
||||||
|
languageChanged: 'Language changed to: **{language}**',
|
||||||
|
currentLanguage: 'Current language: **{language}**',
|
||||||
|
availableLanguages: 'Available languages: {languages}',
|
||||||
|
today: 'Today',
|
||||||
|
tomorrow: 'Tomorrow',
|
||||||
|
dayAfterTomorrow: 'Day after tomorrow',
|
||||||
|
created: 'Created',
|
||||||
|
deleted: 'Deleted',
|
||||||
|
updated: 'Updated',
|
||||||
|
completed: 'Completed',
|
||||||
|
|
||||||
|
// Timer
|
||||||
|
timer: 'Timer',
|
||||||
|
timerStarted: 'Timer started!',
|
||||||
|
timerPaused: 'Timer paused',
|
||||||
|
timerResumed: 'Timer resumed',
|
||||||
|
timerReset: 'Timer reset.',
|
||||||
|
timerFinished: 'Timer finished!',
|
||||||
|
noActiveTimer: 'No active timer.',
|
||||||
|
noPausedTimer: 'No paused timer.',
|
||||||
|
noTimers: 'No timers.',
|
||||||
|
remaining: 'Remaining',
|
||||||
|
duration: 'Duration',
|
||||||
|
label: 'Label',
|
||||||
|
|
||||||
|
// Alarm
|
||||||
|
alarm: 'Alarm',
|
||||||
|
alarmSet: 'Alarm set!',
|
||||||
|
alarmDeleted: 'Alarm deleted.',
|
||||||
|
noAlarms: 'No alarms.',
|
||||||
|
yourAlarms: 'Your alarms',
|
||||||
|
|
||||||
|
// World Clock
|
||||||
|
worldClock: 'World clock',
|
||||||
|
worldClocks: 'World clocks',
|
||||||
|
worldClockAdded: 'World clock added: {city}',
|
||||||
|
noWorldClocks: 'No world clocks.',
|
||||||
|
yourWorldClocks: 'Your world clocks',
|
||||||
|
|
||||||
|
// Time
|
||||||
|
currentTime: 'Current time',
|
||||||
|
|
||||||
|
// Help
|
||||||
|
helpTitle: 'Clock Bot - Help',
|
||||||
|
helpCommands: `**Commands:**
|
||||||
|
• \`!timer 25m\` - Start timer
|
||||||
|
• \`!stop\` - Pause timer
|
||||||
|
• \`!resume\` - Resume timer
|
||||||
|
• \`!reset\` - Reset timer
|
||||||
|
• \`!timers\` - All timers
|
||||||
|
• \`!alarm 07:30\` - Set alarm
|
||||||
|
• \`!alarms\` - All alarms
|
||||||
|
• \`!time\` - Current time
|
||||||
|
• \`!worldclock Berlin\` - Add world clock
|
||||||
|
• \`!worldclocks\` - All world clocks
|
||||||
|
• \`!status\` - Bot status
|
||||||
|
• \`!language [de/en]\` - Change language`,
|
||||||
|
helpExamples: `**Examples:**
|
||||||
|
• \`25\` (25 minute timer)
|
||||||
|
• \`1h30m\` (1.5 hour timer)
|
||||||
|
• \`!alarm 7:30 am\``,
|
||||||
|
|
||||||
|
// Parsing errors
|
||||||
|
couldNotParseDuration: 'Could not parse duration.',
|
||||||
|
couldNotParseTime: 'Could not parse time.',
|
||||||
|
},
|
||||||
|
};
|
||||||
235
packages/bot-services/src/i18n/types.ts
Normal file
235
packages/bot-services/src/i18n/types.ts
Normal file
|
|
@ -0,0 +1,235 @@
|
||||||
|
/**
|
||||||
|
* Supported languages
|
||||||
|
*/
|
||||||
|
export type Language = 'de' | 'en';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common translations shared across all bots
|
||||||
|
*/
|
||||||
|
export interface CommonTranslations {
|
||||||
|
// General
|
||||||
|
error: string;
|
||||||
|
errorOccurred: string;
|
||||||
|
notLoggedIn: string;
|
||||||
|
loginRequired: string;
|
||||||
|
loginSuccess: string;
|
||||||
|
loginFailed: string;
|
||||||
|
logoutSuccess: string;
|
||||||
|
invalidCommand: string;
|
||||||
|
helpHint: string;
|
||||||
|
|
||||||
|
// Credits
|
||||||
|
credits: string;
|
||||||
|
creditsRemaining: string;
|
||||||
|
insufficientCredits: string;
|
||||||
|
buyCredits: string;
|
||||||
|
|
||||||
|
// Sync
|
||||||
|
synced: string;
|
||||||
|
localStorage: string;
|
||||||
|
|
||||||
|
// Status
|
||||||
|
status: string;
|
||||||
|
online: string;
|
||||||
|
offline: string;
|
||||||
|
loggedInAs: string;
|
||||||
|
notLoggedInStatus: string;
|
||||||
|
|
||||||
|
// Language
|
||||||
|
languageChanged: string;
|
||||||
|
currentLanguage: string;
|
||||||
|
availableLanguages: string;
|
||||||
|
|
||||||
|
// Dates
|
||||||
|
today: string;
|
||||||
|
tomorrow: string;
|
||||||
|
dayAfterTomorrow: string;
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
created: string;
|
||||||
|
deleted: string;
|
||||||
|
updated: string;
|
||||||
|
completed: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Todo bot translations
|
||||||
|
*/
|
||||||
|
export interface TodoTranslations extends CommonTranslations {
|
||||||
|
// Tasks
|
||||||
|
task: string;
|
||||||
|
tasks: string;
|
||||||
|
taskCreated: string;
|
||||||
|
taskCompleted: string;
|
||||||
|
taskDeleted: string;
|
||||||
|
noTasks: string;
|
||||||
|
noTasksToday: string;
|
||||||
|
inboxEmpty: string;
|
||||||
|
allTasks: string;
|
||||||
|
todayTasks: string;
|
||||||
|
inbox: string;
|
||||||
|
|
||||||
|
// Projects
|
||||||
|
project: string;
|
||||||
|
projects: string;
|
||||||
|
noProjects: string;
|
||||||
|
projectTasks: string;
|
||||||
|
|
||||||
|
// Priorities
|
||||||
|
priority: string;
|
||||||
|
date: string;
|
||||||
|
|
||||||
|
// Help
|
||||||
|
helpTitle: string;
|
||||||
|
helpCommands: string;
|
||||||
|
helpSyntax: string;
|
||||||
|
helpExamples: string;
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
markDone: string;
|
||||||
|
delete: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calendar bot translations
|
||||||
|
*/
|
||||||
|
export interface CalendarTranslations extends CommonTranslations {
|
||||||
|
// Events
|
||||||
|
event: string;
|
||||||
|
events: string;
|
||||||
|
eventCreated: string;
|
||||||
|
eventDeleted: string;
|
||||||
|
noEvents: string;
|
||||||
|
noEventsToday: string;
|
||||||
|
noEventsTomorrow: string;
|
||||||
|
noEventsThisWeek: string;
|
||||||
|
upcomingEvents: string;
|
||||||
|
todayEvents: string;
|
||||||
|
tomorrowEvents: string;
|
||||||
|
weekEvents: string;
|
||||||
|
|
||||||
|
// Calendars
|
||||||
|
calendar: string;
|
||||||
|
calendars: string;
|
||||||
|
yourCalendars: string;
|
||||||
|
|
||||||
|
// Time
|
||||||
|
time: string;
|
||||||
|
allDay: string;
|
||||||
|
location: string;
|
||||||
|
|
||||||
|
// Help
|
||||||
|
helpTitle: string;
|
||||||
|
helpCommands: string;
|
||||||
|
helpSyntax: string;
|
||||||
|
helpExamples: string;
|
||||||
|
|
||||||
|
// Parsing errors
|
||||||
|
couldNotParseDateTime: string;
|
||||||
|
pleaseProvideTitle: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contacts bot translations
|
||||||
|
*/
|
||||||
|
export interface ContactsTranslations extends CommonTranslations {
|
||||||
|
// Contacts
|
||||||
|
contact: string;
|
||||||
|
contacts: string;
|
||||||
|
contactCreated: string;
|
||||||
|
contactDeleted: string;
|
||||||
|
contactUpdated: string;
|
||||||
|
noContacts: string;
|
||||||
|
|
||||||
|
// Favorites
|
||||||
|
favorite: string;
|
||||||
|
favorites: string;
|
||||||
|
noFavorites: string;
|
||||||
|
markedAsFavorite: string;
|
||||||
|
removedFromFavorites: string;
|
||||||
|
|
||||||
|
// Search
|
||||||
|
search: string;
|
||||||
|
searchResults: string;
|
||||||
|
noSearchResults: string;
|
||||||
|
|
||||||
|
// Fields
|
||||||
|
email: string;
|
||||||
|
phone: string;
|
||||||
|
mobile: string;
|
||||||
|
company: string;
|
||||||
|
jobTitle: string;
|
||||||
|
address: string;
|
||||||
|
website: string;
|
||||||
|
birthday: string;
|
||||||
|
notes: string;
|
||||||
|
|
||||||
|
// Help
|
||||||
|
helpTitle: string;
|
||||||
|
helpCommands: string;
|
||||||
|
helpFields: string;
|
||||||
|
helpExamples: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clock bot translations
|
||||||
|
*/
|
||||||
|
export interface ClockTranslations extends CommonTranslations {
|
||||||
|
// Timer
|
||||||
|
timer: string;
|
||||||
|
timerStarted: string;
|
||||||
|
timerPaused: string;
|
||||||
|
timerResumed: string;
|
||||||
|
timerReset: string;
|
||||||
|
timerFinished: string;
|
||||||
|
noActiveTimer: string;
|
||||||
|
noPausedTimer: string;
|
||||||
|
noTimers: string;
|
||||||
|
remaining: string;
|
||||||
|
duration: string;
|
||||||
|
label: string;
|
||||||
|
|
||||||
|
// Alarm
|
||||||
|
alarm: string;
|
||||||
|
alarmSet: string;
|
||||||
|
alarmDeleted: string;
|
||||||
|
noAlarms: string;
|
||||||
|
yourAlarms: string;
|
||||||
|
|
||||||
|
// World Clock
|
||||||
|
worldClock: string;
|
||||||
|
worldClocks: string;
|
||||||
|
worldClockAdded: string;
|
||||||
|
noWorldClocks: string;
|
||||||
|
yourWorldClocks: string;
|
||||||
|
|
||||||
|
// Time
|
||||||
|
currentTime: string;
|
||||||
|
|
||||||
|
// Help
|
||||||
|
helpTitle: string;
|
||||||
|
helpCommands: string;
|
||||||
|
helpExamples: string;
|
||||||
|
|
||||||
|
// Parsing errors
|
||||||
|
couldNotParseDuration: string;
|
||||||
|
couldNotParseTime: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All bot translations combined
|
||||||
|
*/
|
||||||
|
export interface BotTranslations {
|
||||||
|
common: CommonTranslations;
|
||||||
|
todo: TodoTranslations;
|
||||||
|
calendar: CalendarTranslations;
|
||||||
|
contacts: ContactsTranslations;
|
||||||
|
clock: ClockTranslations;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* I18n service options
|
||||||
|
*/
|
||||||
|
export interface I18nOptions {
|
||||||
|
defaultLanguage?: Language;
|
||||||
|
}
|
||||||
|
|
@ -136,6 +136,20 @@ export type {
|
||||||
CreditStatusMessage,
|
CreditStatusMessage,
|
||||||
} from './credit/index.js';
|
} from './credit/index.js';
|
||||||
|
|
||||||
|
// I18n (Multi-language support for Matrix bots)
|
||||||
|
export { I18nModule, I18nService, I18N_OPTIONS, LANGUAGE_NAMES } from './i18n/index.js';
|
||||||
|
export type {
|
||||||
|
Language,
|
||||||
|
I18nOptions,
|
||||||
|
BotTranslations,
|
||||||
|
CommonTranslations,
|
||||||
|
TodoTranslations,
|
||||||
|
CalendarTranslations,
|
||||||
|
ContactsTranslations,
|
||||||
|
ClockTranslations,
|
||||||
|
} from './i18n/index.js';
|
||||||
|
export { de as deTranslations, en as enTranslations } from './i18n/index.js';
|
||||||
|
|
||||||
// ===== Placeholder Services (to be implemented) =====
|
// ===== Placeholder Services (to be implemented) =====
|
||||||
|
|
||||||
export { NutritionModule } from './nutrition/index.js';
|
export { NutritionModule } from './nutrition/index.js';
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import {
|
||||||
SessionModule,
|
SessionModule,
|
||||||
CreditModule,
|
CreditModule,
|
||||||
CalendarApiService,
|
CalendarApiService,
|
||||||
|
I18nModule,
|
||||||
} from '@manacore/bot-services';
|
} from '@manacore/bot-services';
|
||||||
|
|
||||||
// Factory provider for CalendarApiService
|
// Factory provider for CalendarApiService
|
||||||
|
|
@ -28,6 +29,7 @@ const calendarApiServiceProvider = {
|
||||||
}),
|
}),
|
||||||
SessionModule.forRoot({ storageMode: 'redis' }),
|
SessionModule.forRoot({ storageMode: 'redis' }),
|
||||||
CreditModule.forRoot(),
|
CreditModule.forRoot(),
|
||||||
|
I18nModule.forRoot(),
|
||||||
],
|
],
|
||||||
providers: [MatrixService, calendarApiServiceProvider],
|
providers: [MatrixService, calendarApiServiceProvider],
|
||||||
exports: [MatrixService],
|
exports: [MatrixService],
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,9 @@ import {
|
||||||
CreditService,
|
CreditService,
|
||||||
CalendarApiService,
|
CalendarApiService,
|
||||||
CalendarEvent as ApiCalendarEvent,
|
CalendarEvent as ApiCalendarEvent,
|
||||||
|
I18nService,
|
||||||
|
Language,
|
||||||
|
LANGUAGE_NAMES,
|
||||||
} from '@manacore/bot-services';
|
} from '@manacore/bot-services';
|
||||||
import { CalendarService, CalendarEvent } from '../calendar/calendar.service';
|
import { CalendarService, CalendarEvent } from '../calendar/calendar.service';
|
||||||
import { HELP_TEXT, WELCOME_TEXT, BOT_INTRODUCTION } from '../config/configuration';
|
import { HELP_TEXT, WELCOME_TEXT, BOT_INTRODUCTION } from '../config/configuration';
|
||||||
|
|
@ -43,7 +46,8 @@ export class MatrixService extends BaseMatrixService {
|
||||||
private calendarService: CalendarService,
|
private calendarService: CalendarService,
|
||||||
private calendarApiService: CalendarApiService,
|
private calendarApiService: CalendarApiService,
|
||||||
private sessionService: SessionService,
|
private sessionService: SessionService,
|
||||||
private creditService: CreditService
|
private creditService: CreditService,
|
||||||
|
private i18nService: I18nService
|
||||||
) {
|
) {
|
||||||
super(configService);
|
super(configService);
|
||||||
}
|
}
|
||||||
|
|
@ -132,6 +136,9 @@ export class MatrixService extends BaseMatrixService {
|
||||||
await this.executeCommand(roomId, event, sender, keywordCommand, '');
|
await this.executeCommand(roomId, event, sender, keywordCommand, '');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fallback: treat any message as an event
|
||||||
|
await this.handleCreateEvent(roomId, event, sender, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async executeCommand(
|
private async executeCommand(
|
||||||
|
|
@ -207,6 +214,12 @@ export class MatrixService extends BaseMatrixService {
|
||||||
await this.handleLogout(roomId, event, userId);
|
await this.handleLogout(roomId, event, userId);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'language':
|
||||||
|
case 'sprache':
|
||||||
|
case 'lang':
|
||||||
|
await this.handleLanguage(roomId, event, userId, args);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// Unknown command - ignore silently
|
// Unknown command - ignore silently
|
||||||
break;
|
break;
|
||||||
|
|
@ -695,4 +708,47 @@ export class MatrixService extends BaseMatrixService {
|
||||||
this.logger.error(`Failed to send welcome message: ${error}`);
|
this.logger.error(`Failed to send welcome message: ${error}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async handleLanguage(
|
||||||
|
roomId: string,
|
||||||
|
event: MatrixRoomEvent,
|
||||||
|
userId: string,
|
||||||
|
args: string
|
||||||
|
) {
|
||||||
|
const lang = args.trim().toLowerCase();
|
||||||
|
|
||||||
|
if (!lang) {
|
||||||
|
const currentLang = await this.i18nService.getLanguage(userId);
|
||||||
|
const langName = LANGUAGE_NAMES[currentLang];
|
||||||
|
const available = this.i18nService
|
||||||
|
.getAvailableLanguages()
|
||||||
|
.map((l) => `${l} (${LANGUAGE_NAMES[l]})`)
|
||||||
|
.join(', ');
|
||||||
|
await this.sendReply(
|
||||||
|
roomId,
|
||||||
|
event,
|
||||||
|
`**Sprache / Language:** ${langName}\n\n**Verfügbar / Available:** ${available}\n\nÄndern / Change: \`!language de\` oder / or \`!language en\``
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.i18nService.isValidLanguage(lang)) {
|
||||||
|
const available = this.i18nService.getAvailableLanguages().join(', ');
|
||||||
|
await this.sendReply(
|
||||||
|
roomId,
|
||||||
|
event,
|
||||||
|
`Unbekannte Sprache / Unknown language: ${lang}\n\nVerfügbar / Available: ${available}`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.i18nService.setLanguage(userId, lang as Language);
|
||||||
|
const langName = LANGUAGE_NAMES[lang as Language];
|
||||||
|
|
||||||
|
if (lang === 'de') {
|
||||||
|
await this.sendReply(roomId, event, `Sprache geändert zu: **${langName}**`);
|
||||||
|
} else {
|
||||||
|
await this.sendReply(roomId, event, `Language changed to: **${langName}**`);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,21 @@
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { MatrixService } from './matrix.service';
|
import { MatrixService } from './matrix.service';
|
||||||
import { ClockModule } from '../clock/clock.module';
|
import { ClockModule } from '../clock/clock.module';
|
||||||
import { TranscriptionModule, SessionModule, CreditModule } from '@manacore/bot-services';
|
import {
|
||||||
|
TranscriptionModule,
|
||||||
|
SessionModule,
|
||||||
|
CreditModule,
|
||||||
|
I18nModule,
|
||||||
|
} from '@manacore/bot-services';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [ClockModule, TranscriptionModule.forRoot(), SessionModule.forRoot(), CreditModule.forRoot()],
|
imports: [
|
||||||
|
ClockModule,
|
||||||
|
TranscriptionModule.forRoot(),
|
||||||
|
SessionModule.forRoot(),
|
||||||
|
CreditModule.forRoot(),
|
||||||
|
I18nModule.forRoot(),
|
||||||
|
],
|
||||||
providers: [MatrixService],
|
providers: [MatrixService],
|
||||||
exports: [MatrixService],
|
exports: [MatrixService],
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,14 @@ import {
|
||||||
COMMON_KEYWORDS,
|
COMMON_KEYWORDS,
|
||||||
} from '@manacore/matrix-bot-common';
|
} from '@manacore/matrix-bot-common';
|
||||||
import { ClockService } from '../clock/clock.service';
|
import { ClockService } from '../clock/clock.service';
|
||||||
import { TranscriptionService, SessionService, CreditService } from '@manacore/bot-services';
|
import {
|
||||||
|
TranscriptionService,
|
||||||
|
SessionService,
|
||||||
|
CreditService,
|
||||||
|
I18nService,
|
||||||
|
Language,
|
||||||
|
LANGUAGE_NAMES,
|
||||||
|
} from '@manacore/bot-services';
|
||||||
import { HELP_TEXT, WELCOME_TEXT } from '../config/configuration';
|
import { HELP_TEXT, WELCOME_TEXT } from '../config/configuration';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
|
@ -30,7 +37,8 @@ export class MatrixService extends BaseMatrixService {
|
||||||
private clockService: ClockService,
|
private clockService: ClockService,
|
||||||
private transcriptionService: TranscriptionService,
|
private transcriptionService: TranscriptionService,
|
||||||
private sessionService: SessionService,
|
private sessionService: SessionService,
|
||||||
private creditService: CreditService
|
private creditService: CreditService,
|
||||||
|
private i18nService: I18nService
|
||||||
) {
|
) {
|
||||||
super(configService);
|
super(configService);
|
||||||
}
|
}
|
||||||
|
|
@ -218,6 +226,12 @@ export class MatrixService extends BaseMatrixService {
|
||||||
await this.handleWorldClocksCommand(roomId, event, userId);
|
await this.handleWorldClocksCommand(roomId, event, userId);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'language':
|
||||||
|
case 'sprache':
|
||||||
|
case 'lang':
|
||||||
|
await this.handleLanguage(roomId, event, userId, args);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// Silently ignore unknown commands
|
// Silently ignore unknown commands
|
||||||
break;
|
break;
|
||||||
|
|
@ -676,7 +690,12 @@ export class MatrixService extends BaseMatrixService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// No match - don't respond to random messages
|
// Fallback: try to parse any message as a timer duration
|
||||||
|
const duration = this.clockService.parseDuration(text);
|
||||||
|
if (duration) {
|
||||||
|
await this.handleTimerCommand(roomId, event, userId, text);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getToken(userId: string): Promise<string | null> {
|
private async getToken(userId: string): Promise<string | null> {
|
||||||
|
|
@ -691,4 +710,47 @@ export class MatrixService extends BaseMatrixService {
|
||||||
// Entwicklungs-Fallback
|
// Entwicklungs-Fallback
|
||||||
return this.demoToken || null;
|
return this.demoToken || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async handleLanguage(
|
||||||
|
roomId: string,
|
||||||
|
event: MatrixRoomEvent,
|
||||||
|
userId: string,
|
||||||
|
args: string
|
||||||
|
) {
|
||||||
|
const lang = args.trim().toLowerCase();
|
||||||
|
|
||||||
|
if (!lang) {
|
||||||
|
const currentLang = await this.i18nService.getLanguage(userId);
|
||||||
|
const langName = LANGUAGE_NAMES[currentLang];
|
||||||
|
const available = this.i18nService
|
||||||
|
.getAvailableLanguages()
|
||||||
|
.map((l) => `${l} (${LANGUAGE_NAMES[l]})`)
|
||||||
|
.join(', ');
|
||||||
|
await this.sendReply(
|
||||||
|
roomId,
|
||||||
|
event,
|
||||||
|
`**Sprache / Language:** ${langName}\n\n**Verfügbar / Available:** ${available}\n\nÄndern / Change: \`!language de\` oder / or \`!language en\``
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.i18nService.isValidLanguage(lang)) {
|
||||||
|
const available = this.i18nService.getAvailableLanguages().join(', ');
|
||||||
|
await this.sendReply(
|
||||||
|
roomId,
|
||||||
|
event,
|
||||||
|
`Unbekannte Sprache / Unknown language: ${lang}\n\nVerfügbar / Available: ${available}`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.i18nService.setLanguage(userId, lang as Language);
|
||||||
|
const langName = LANGUAGE_NAMES[lang as Language];
|
||||||
|
|
||||||
|
if (lang === 'de') {
|
||||||
|
await this.sendReply(roomId, event, `Sprache geändert zu: **${langName}**`);
|
||||||
|
} else {
|
||||||
|
await this.sendReply(roomId, event, `Language changed to: **${langName}**`);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -186,27 +186,27 @@ export class ClockService {
|
||||||
parseDuration(input: string): number | null {
|
parseDuration(input: string): number | null {
|
||||||
let totalSeconds = 0;
|
let totalSeconds = 0;
|
||||||
|
|
||||||
// Match hours
|
// Match hours: 1h, 1 h, 1 stunde, 1 stunden, 1 hour, 1 hours
|
||||||
const hoursMatch = input.match(/(\d+)\s*h/i);
|
const hoursMatch = input.match(/(\d+)\s*(?:h|stunde[n]?|hour[s]?)\b/i);
|
||||||
if (hoursMatch) {
|
if (hoursMatch) {
|
||||||
totalSeconds += parseInt(hoursMatch[1], 10) * 3600;
|
totalSeconds += parseInt(hoursMatch[1], 10) * 3600;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match minutes
|
// Match minutes: 25m, 25 m, 25min, 25 min, 25 minuten, 25 minute, 25 minutes
|
||||||
const minutesMatch = input.match(/(\d+)\s*m(?:in)?/i);
|
const minutesMatch = input.match(/(\d+)\s*(?:m|min|minute[n]?|minutes?)\b/i);
|
||||||
if (minutesMatch) {
|
if (minutesMatch) {
|
||||||
totalSeconds += parseInt(minutesMatch[1], 10) * 60;
|
totalSeconds += parseInt(minutesMatch[1], 10) * 60;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match seconds
|
// Match seconds: 30s, 30 s, 30sec, 30 sec, 30 sekunden, 30 seconds
|
||||||
const secondsMatch = input.match(/(\d+)\s*s(?:ec)?/i);
|
const secondsMatch = input.match(/(\d+)\s*(?:s|sec|sekunde[n]?|seconds?)\b/i);
|
||||||
if (secondsMatch) {
|
if (secondsMatch) {
|
||||||
totalSeconds += parseInt(secondsMatch[1], 10);
|
totalSeconds += parseInt(secondsMatch[1], 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If just a number, assume minutes
|
// If just a number (with optional whitespace), assume minutes
|
||||||
if (totalSeconds === 0) {
|
if (totalSeconds === 0) {
|
||||||
const justNumber = input.match(/^(\d+)$/);
|
const justNumber = input.trim().match(/^(\d+)$/);
|
||||||
if (justNumber) {
|
if (justNumber) {
|
||||||
totalSeconds = parseInt(justNumber[1], 10) * 60;
|
totalSeconds = parseInt(justNumber[1], 10) * 60;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,12 @@
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { MatrixService } from './matrix.service';
|
import { MatrixService } from './matrix.service';
|
||||||
import { ContactsModule } from '../contacts/contacts.module';
|
import { ContactsModule } from '../contacts/contacts.module';
|
||||||
import { SessionModule, TranscriptionModule, CreditModule } from '@manacore/bot-services';
|
import {
|
||||||
|
SessionModule,
|
||||||
|
TranscriptionModule,
|
||||||
|
CreditModule,
|
||||||
|
I18nModule,
|
||||||
|
} from '@manacore/bot-services';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
|
|
@ -11,6 +16,7 @@ import { SessionModule, TranscriptionModule, CreditModule } from '@manacore/bot-
|
||||||
sttUrl: process.env.STT_URL || 'http://localhost:3020',
|
sttUrl: process.env.STT_URL || 'http://localhost:3020',
|
||||||
}),
|
}),
|
||||||
CreditModule.forRoot(),
|
CreditModule.forRoot(),
|
||||||
|
I18nModule.forRoot(),
|
||||||
],
|
],
|
||||||
providers: [MatrixService],
|
providers: [MatrixService],
|
||||||
exports: [MatrixService],
|
exports: [MatrixService],
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,14 @@ import {
|
||||||
UserListMapper,
|
UserListMapper,
|
||||||
} from '@manacore/matrix-bot-common';
|
} from '@manacore/matrix-bot-common';
|
||||||
import { ContactsService, Contact } from '../contacts/contacts.service';
|
import { ContactsService, Contact } from '../contacts/contacts.service';
|
||||||
import { SessionService, TranscriptionService, CreditService } from '@manacore/bot-services';
|
import {
|
||||||
|
SessionService,
|
||||||
|
TranscriptionService,
|
||||||
|
CreditService,
|
||||||
|
I18nService,
|
||||||
|
Language,
|
||||||
|
LANGUAGE_NAMES,
|
||||||
|
} from '@manacore/bot-services';
|
||||||
import { HELP_MESSAGE } from '../config/configuration';
|
import { HELP_MESSAGE } from '../config/configuration';
|
||||||
|
|
||||||
const CONTACT_CREATE_CREDITS = 0.02;
|
const CONTACT_CREATE_CREDITS = 0.02;
|
||||||
|
|
@ -32,7 +39,8 @@ export class MatrixService extends BaseMatrixService {
|
||||||
private readonly transcriptionService: TranscriptionService,
|
private readonly transcriptionService: TranscriptionService,
|
||||||
private contactsService: ContactsService,
|
private contactsService: ContactsService,
|
||||||
private sessionService: SessionService,
|
private sessionService: SessionService,
|
||||||
private creditService: CreditService
|
private creditService: CreditService,
|
||||||
|
private i18nService: I18nService
|
||||||
) {
|
) {
|
||||||
super(configService);
|
super(configService);
|
||||||
}
|
}
|
||||||
|
|
@ -102,6 +110,10 @@ Sag "hilfe" fur alle Befehle!`;
|
||||||
await this.handleCommand(roomId, event, sender, `!${detectedCommand}`);
|
await this.handleCommand(roomId, event, sender, `!${detectedCommand}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fallback: treat any message as a new contact
|
||||||
|
const args = message.trim().split(/\s+/);
|
||||||
|
await this.handleCreateContact(roomId, event, sender, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleCommand(
|
private async handleCommand(
|
||||||
|
|
@ -188,6 +200,12 @@ Sag "hilfe" fur alle Befehle!`;
|
||||||
await this.pinHelpMessage(roomId, event);
|
await this.pinHelpMessage(roomId, event);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'language':
|
||||||
|
case 'sprache':
|
||||||
|
case 'lang':
|
||||||
|
await this.handleLanguage(roomId, event, sender, argString);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
await this.sendReply(
|
await this.sendReply(
|
||||||
roomId,
|
roomId,
|
||||||
|
|
@ -766,4 +784,47 @@ Sag "hilfe" fur alle Befehle!`;
|
||||||
await this.sendReply(roomId, event, 'Fehler beim Pinnen der Hilfe.');
|
await this.sendReply(roomId, event, 'Fehler beim Pinnen der Hilfe.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async handleLanguage(
|
||||||
|
roomId: string,
|
||||||
|
event: MatrixRoomEvent,
|
||||||
|
userId: string,
|
||||||
|
args: string
|
||||||
|
) {
|
||||||
|
const lang = args.trim().toLowerCase();
|
||||||
|
|
||||||
|
if (!lang) {
|
||||||
|
const currentLang = await this.i18nService.getLanguage(userId);
|
||||||
|
const langName = LANGUAGE_NAMES[currentLang];
|
||||||
|
const available = this.i18nService
|
||||||
|
.getAvailableLanguages()
|
||||||
|
.map((l) => `${l} (${LANGUAGE_NAMES[l]})`)
|
||||||
|
.join(', ');
|
||||||
|
await this.sendReply(
|
||||||
|
roomId,
|
||||||
|
event,
|
||||||
|
`**Sprache / Language:** ${langName}\n\n**Verfügbar / Available:** ${available}\n\nÄndern / Change: \`!language de\` oder / or \`!language en\``
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.i18nService.isValidLanguage(lang)) {
|
||||||
|
const available = this.i18nService.getAvailableLanguages().join(', ');
|
||||||
|
await this.sendReply(
|
||||||
|
roomId,
|
||||||
|
event,
|
||||||
|
`Unbekannte Sprache / Unknown language: ${lang}\n\nVerfügbar / Available: ${available}`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.i18nService.setLanguage(userId, lang as Language);
|
||||||
|
const langName = LANGUAGE_NAMES[lang as Language];
|
||||||
|
|
||||||
|
if (lang === 'de') {
|
||||||
|
await this.sendReply(roomId, event, `Sprache geändert zu: **${langName}**`);
|
||||||
|
} else {
|
||||||
|
await this.sendReply(roomId, event, `Language changed to: **${langName}**`);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import {
|
||||||
SessionModule,
|
SessionModule,
|
||||||
CreditModule,
|
CreditModule,
|
||||||
TodoApiService,
|
TodoApiService,
|
||||||
|
I18nModule,
|
||||||
} from '@manacore/bot-services';
|
} from '@manacore/bot-services';
|
||||||
|
|
||||||
// Factory provider for TodoApiService
|
// Factory provider for TodoApiService
|
||||||
|
|
@ -26,6 +27,7 @@ const todoApiServiceProvider = {
|
||||||
TranscriptionModule.forRoot(),
|
TranscriptionModule.forRoot(),
|
||||||
SessionModule.forRoot({ storageMode: 'redis' }),
|
SessionModule.forRoot({ storageMode: 'redis' }),
|
||||||
CreditModule.forRoot(),
|
CreditModule.forRoot(),
|
||||||
|
I18nModule.forRoot(),
|
||||||
],
|
],
|
||||||
providers: [MatrixService, todoApiServiceProvider],
|
providers: [MatrixService, todoApiServiceProvider],
|
||||||
exports: [MatrixService],
|
exports: [MatrixService],
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,9 @@ import {
|
||||||
CreditService,
|
CreditService,
|
||||||
TodoApiService,
|
TodoApiService,
|
||||||
Task as ApiTask,
|
Task as ApiTask,
|
||||||
|
I18nService,
|
||||||
|
Language,
|
||||||
|
LANGUAGE_NAMES,
|
||||||
} from '@manacore/bot-services';
|
} from '@manacore/bot-services';
|
||||||
import { HELP_TEXT, WELCOME_TEXT, BOT_INTRODUCTION } from '../config/configuration';
|
import { HELP_TEXT, WELCOME_TEXT, BOT_INTRODUCTION } from '../config/configuration';
|
||||||
|
|
||||||
|
|
@ -44,7 +47,8 @@ export class MatrixService extends BaseMatrixService {
|
||||||
private todoApiService: TodoApiService,
|
private todoApiService: TodoApiService,
|
||||||
private transcriptionService: TranscriptionService,
|
private transcriptionService: TranscriptionService,
|
||||||
private sessionService: SessionService,
|
private sessionService: SessionService,
|
||||||
private creditService: CreditService
|
private creditService: CreditService,
|
||||||
|
private i18nService: I18nService
|
||||||
) {
|
) {
|
||||||
super(configService);
|
super(configService);
|
||||||
}
|
}
|
||||||
|
|
@ -108,7 +112,11 @@ export class MatrixService extends BaseMatrixService {
|
||||||
if (body.startsWith('!')) {
|
if (body.startsWith('!')) {
|
||||||
const [command, ...args] = body.slice(1).split(' ');
|
const [command, ...args] = body.slice(1).split(' ');
|
||||||
await this.executeCommand(roomId, event, userId, command.toLowerCase(), args.join(' '));
|
await this.executeCommand(roomId, event, userId, command.toLowerCase(), args.join(' '));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fallback: treat any message as a task
|
||||||
|
await this.handleAddTask(roomId, event, userId, body);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(`Error handling message: ${error}`);
|
this.logger.error(`Error handling message: ${error}`);
|
||||||
await this.sendReply(roomId, event, 'Ein Fehler ist aufgetreten. Bitte versuche es erneut.');
|
await this.sendReply(roomId, event, 'Ein Fehler ist aufgetreten. Bitte versuche es erneut.');
|
||||||
|
|
@ -299,12 +307,64 @@ export class MatrixService extends BaseMatrixService {
|
||||||
await this.handlePinHelp(roomId, event);
|
await this.handlePinHelp(roomId, event);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'language':
|
||||||
|
case 'sprache':
|
||||||
|
case 'lang':
|
||||||
|
await this.handleLanguage(roomId, event, userId, args);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// Unknown command - ignore silently or send help
|
// Unknown command - ignore silently or send help
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async handleLanguage(
|
||||||
|
roomId: string,
|
||||||
|
event: MatrixRoomEvent,
|
||||||
|
userId: string,
|
||||||
|
args: string
|
||||||
|
) {
|
||||||
|
const lang = args.trim().toLowerCase();
|
||||||
|
|
||||||
|
// Show current language if no argument
|
||||||
|
if (!lang) {
|
||||||
|
const currentLang = await this.i18nService.getLanguage(userId);
|
||||||
|
const langName = LANGUAGE_NAMES[currentLang];
|
||||||
|
const available = this.i18nService
|
||||||
|
.getAvailableLanguages()
|
||||||
|
.map((l) => `${l} (${LANGUAGE_NAMES[l]})`)
|
||||||
|
.join(', ');
|
||||||
|
await this.sendReply(
|
||||||
|
roomId,
|
||||||
|
event,
|
||||||
|
`**Sprache / Language:** ${langName}\n\n**Verfügbar / Available:** ${available}\n\nÄndern / Change: \`!language de\` oder / or \`!language en\``
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate and set language
|
||||||
|
if (!this.i18nService.isValidLanguage(lang)) {
|
||||||
|
const available = this.i18nService.getAvailableLanguages().join(', ');
|
||||||
|
await this.sendReply(
|
||||||
|
roomId,
|
||||||
|
event,
|
||||||
|
`Unbekannte Sprache / Unknown language: ${lang}\n\nVerfügbar / Available: ${available}`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.i18nService.setLanguage(userId, lang as Language);
|
||||||
|
const langName = LANGUAGE_NAMES[lang as Language];
|
||||||
|
|
||||||
|
// Respond in the new language
|
||||||
|
if (lang === 'de') {
|
||||||
|
await this.sendReply(roomId, event, `Sprache geändert zu: **${langName}**`);
|
||||||
|
} else {
|
||||||
|
await this.sendReply(roomId, event, `Language changed to: **${langName}**`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async handleAddTask(
|
private async handleAddTask(
|
||||||
roomId: string,
|
roomId: string,
|
||||||
event: MatrixRoomEvent,
|
event: MatrixRoomEvent,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue