···11+{
22+ "settings": {
33+ "title": "إعدادات AI Tagger",
44+ "provider": {
55+ "title": "مزود الذكاء الاصطناعي",
66+ "desc": "اختر مزود الذكاء الاصطناعي لإنشاء العلامات.",
77+ "anthropic": "Anthropic (Claude)",
88+ "openai": "OpenAI (GPT)",
99+ "mistral": "Mistral AI",
1010+ "google": "Google (Gemini)",
1111+ "custom": "نقطة نهاية مخصصة (متوافقة مع OpenAI)"
1212+ },
1313+ "customEndpoint": {
1414+ "title": "نقطة نهاية API مخصصة",
1515+ "desc": "أدخل عنوان URL لنقطة نهاية API المتوافقة مع OpenAI.",
1616+ "placeholder": "https://your-api-endpoint.com/v1/chat/completions"
1717+ },
1818+ "apiKey": {
1919+ "title": "مفتاح API",
2020+ "desc": "مفتاح API الخاص بـ {provider}. مطلوب لاستخدام خدمة الذكاء الاصطناعي.",
2121+ "getKey": "احصل عليه من {url} إذا لم يكن لديك واحد بالفعل.",
2222+ "recommendation": "نوصي باستخدام مفتاح مخصص لهذا البرنامج المساعد.",
2323+ "placeholder": "أدخل مفتاح API الخاص بك"
2424+ },
2525+ "model": {
2626+ "title": "نموذج الذكاء الاصطناعي",
2727+ "desc": "اختر نموذج الذكاء الاصطناعي المستخدم لإنشاء العلامات.",
2828+ "custom": "{model} (مخصص)"
2929+ },
3030+ "maxTags": {
3131+ "title": "الحد الأقصى لعدد العلامات",
3232+ "desc": "حدد الحد الأقصى لعدد العلامات لإنشائها لكل ملاحظة."
3333+ },
3434+ "promptStyle": {
3535+ "title": "نمط المطالبة",
3636+ "desc": "اختر نمط مطالبة محدد مسبقًا أو قم بإنشاء مطالبة مخصصة خاصة بك.",
3737+ "standard": "قياسي",
3838+ "descriptive": "وصفي",
3939+ "academic": "أكاديمي",
4040+ "concise": "موجز",
4141+ "custom": "مخصص"
4242+ },
4343+ "customPrompt": {
4444+ "title": "قالب مطالبة مخصص",
4545+ "desc": "قم بتخصيص المطالبة المرسلة إلى الذكاء الاصطناعي. استخدم {maxTags} و {content} كعناصر نائبة."
4646+ },
4747+ "currentPrompt": {
4848+ "title": "قالب المطالبة الحالي",
4949+ "desc": "هذا هو قالب المطالبة الذي سيتم استخدامه (للقراءة فقط). قم بالتبديل إلى مخصص إذا كنت ترغب في تعديله."
5050+ },
5151+ "language": {
5252+ "title": "اللغة",
5353+ "desc": "اختر لغة واجهة البرنامج المساعد."
5454+ }
5555+ },
5656+ "modals": {
5757+ "confirm": "تأكيد",
5858+ "cancel": "إلغاء",
5959+ "settings": "فتح الإعدادات",
6060+ "configError": "خطأ في تكوين علامات الذكاء الاصطناعي",
6161+ "tagAll": "سيقوم هذا بوضع علامات على جميع الملاحظات في خزانتك باستخدام نموذج {model}. قد يستغرق هذا بعض الوقت ويستهلك رصيد API. هل تريد المتابعة؟"
6262+ },
6363+ "notices": {
6464+ "noActiveNote": "لا توجد ملاحظة نشطة للتعليم",
6565+ "analyzing": "تحليل محتوى الملاحظة وإنشاء العلامات...",
6666+ "tagSuccess": "تمت إضافة العلامات بنجاح: {tags}",
6767+ "tagError": "خطأ في وضع علامة على الملاحظة: {error}",
6868+ "startingBulk": "بدء وضع علامات على الملاحظات...",
6969+ "processingNote": "معالجة: {file}\nالتقدم: {processed}/{total} ({successful} ناجح)",
7070+ "bulkComplete": "اكتمل وضع العلامات: {successful}/{total} ملاحظات بنجاح",
7171+ "bulkError": "خطأ أثناء وضع العلامات بالجملة: {error}"
7272+ },
7373+ "errors": {
7474+ "apiKeyMissing": "مفتاح API غير مكوّن. يرجى إضافة مفتاح API الخاص بك في إعدادات البرنامج المساعد.",
7575+ "endpointMissing": "نقطة نهاية API المخصصة غير مكوّنة. يرجى إضافة عنوان URL الخاص بنقطة النهاية في إعدادات البرنامج المساعد.",
7676+ "apiError": "فشل في إنشاء العلامات: {error}",
7777+ "unknownProvider": "مزود غير معروف: {provider}"
7878+ },
7979+ "commands": {
8080+ "tagCurrent": "وضع علامة على الملاحظة الحالية باستخدام الذكاء الاصطناعي",
8181+ "tagAll": "وضع علامات على جميع الملاحظات باستخدام الذكاء الاصطناعي"
8282+ },
8383+ "ribbon": {
8484+ "tooltip": "وضع علامات تلقائية باستخدام الذكاء الاصطناعي"
8585+ }
8686+}
+86
src/i18n/locales/de.json
···11+{
22+ "settings": {
33+ "title": "AI Tagger Einstellungen",
44+ "provider": {
55+ "title": "KI-Anbieter",
66+ "desc": "Wählen Sie, welchen KI-Anbieter Sie für die Generierung von Tags verwenden möchten.",
77+ "anthropic": "Anthropic (Claude)",
88+ "openai": "OpenAI (GPT)",
99+ "mistral": "Mistral AI",
1010+ "google": "Google (Gemini)",
1111+ "custom": "Benutzerdefinierter Endpunkt (OpenAI-kompatibel)"
1212+ },
1313+ "customEndpoint": {
1414+ "title": "Benutzerdefinierter API-Endpunkt",
1515+ "desc": "Geben Sie die URL für Ihren benutzerdefinierten OpenAI-kompatiblen API-Endpunkt ein.",
1616+ "placeholder": "https://your-api-endpoint.com/v1/chat/completions"
1717+ },
1818+ "apiKey": {
1919+ "title": "API-Schlüssel",
2020+ "desc": "Ihr {provider} API-Schlüssel. Erforderlich, um den KI-Dienst zu nutzen.",
2121+ "getKey": "Holen Sie ihn von {url}, wenn Sie noch keinen haben.",
2222+ "recommendation": "Wir empfehlen, einen dedizierten Schlüssel für dieses Plugin zu verwenden.",
2323+ "placeholder": "Geben Sie Ihren API-Schlüssel ein"
2424+ },
2525+ "model": {
2626+ "title": "KI-Modell",
2727+ "desc": "Wählen Sie, welches KI-Modell für die Tag-Generierung verwendet werden soll.",
2828+ "custom": "{model} (Benutzerdefiniert)"
2929+ },
3030+ "maxTags": {
3131+ "title": "Maximale Anzahl an Tags",
3232+ "desc": "Legen Sie die maximale Anzahl an Tags fest, die pro Notiz generiert werden sollen."
3333+ },
3434+ "promptStyle": {
3535+ "title": "Prompt-Stil",
3636+ "desc": "Wählen Sie einen vordefinierten Prompt-Stil oder erstellen Sie Ihren eigenen benutzerdefinierten Prompt.",
3737+ "standard": "Standard",
3838+ "descriptive": "Beschreibend",
3939+ "academic": "Akademisch",
4040+ "concise": "Prägnant",
4141+ "custom": "Benutzerdefiniert"
4242+ },
4343+ "customPrompt": {
4444+ "title": "Benutzerdefinierte Prompt-Vorlage",
4545+ "desc": "Passen Sie den an die KI gesendeten Prompt an. Verwenden Sie {maxTags} und {content} als Platzhalter."
4646+ },
4747+ "currentPrompt": {
4848+ "title": "Aktuelle Prompt-Vorlage",
4949+ "desc": "Dies ist die Prompt-Vorlage, die verwendet wird (schreibgeschützt). Wechseln Sie zu Benutzerdefiniert, wenn Sie sie bearbeiten möchten."
5050+ },
5151+ "language": {
5252+ "title": "Sprache",
5353+ "desc": "Wählen Sie die Sprache für die Plugin-Oberfläche."
5454+ }
5555+ },
5656+ "modals": {
5757+ "confirm": "Bestätigen",
5858+ "cancel": "Abbrechen",
5959+ "settings": "Einstellungen öffnen",
6060+ "configError": "KI-Tagging Konfigurationsfehler",
6161+ "tagAll": "Dies wird alle Notizen in Ihrem Vault mit dem Modell {model} taggen. Dies kann einige Zeit dauern und API-Guthaben verbrauchen. Möchten Sie fortfahren?"
6262+ },
6363+ "notices": {
6464+ "noActiveNote": "Keine aktive Notiz zum Taggen",
6565+ "analyzing": "Analysiere Notizinhalt und generiere Tags...",
6666+ "tagSuccess": "Tags erfolgreich hinzugefügt: {tags}",
6767+ "tagError": "Fehler beim Taggen der Notiz: {error}",
6868+ "startingBulk": "Beginne mit dem Taggen von Notizen...",
6969+ "processingNote": "Verarbeite: {file}\nFortschritt: {processed}/{total} ({successful} erfolgreich)",
7070+ "bulkComplete": "Tagging von {successful}/{total} Notizen erfolgreich abgeschlossen",
7171+ "bulkError": "Fehler beim Massen-Tagging: {error}"
7272+ },
7373+ "errors": {
7474+ "apiKeyMissing": "API-Schlüssel nicht konfiguriert. Bitte fügen Sie Ihren API-Schlüssel in den Plugin-Einstellungen hinzu.",
7575+ "endpointMissing": "Benutzerdefinierter API-Endpunkt nicht konfiguriert. Bitte fügen Sie Ihre Endpunkt-URL in den Plugin-Einstellungen hinzu.",
7676+ "apiError": "Fehler beim Generieren von Tags: {error}",
7777+ "unknownProvider": "Unbekannter Anbieter: {provider}"
7878+ },
7979+ "commands": {
8080+ "tagCurrent": "Aktuelle Notiz mit KI taggen",
8181+ "tagAll": "Alle Notizen mit KI taggen"
8282+ },
8383+ "ribbon": {
8484+ "tooltip": "Auto-Tagging mit KI"
8585+ }
8686+}
+9-5
src/i18n/locales/index.ts
···11-import en from './en.json';
22-import es from './es.json';
11+import ar from "./ar.json";
22+import de from "./de.json";
33+import en from "./en.json";
44+import es from "./es.json";
3546export type TranslationKey = keyof typeof en;
5768export const locales = {
99+ ar,
1010+ de,
711 en,
812 es,
99- // Add more languages here
1013};
11141215export const languageNames = {
1616+ ar: "العربية",
1717+ de: "Deutsch",
1318 en: "English",
1419 es: "Español",
1515- // Add more language names here
1616-};2020+};
+88-6
src/i18n/translationService.ts
···11import { locales } from './locales';
22-import { Language } from '../models/types';
22+import { LanguageCode } from '../models/types';
33+import { App } from 'obsidian';
3445class TranslationService {
55- private currentLanguage: Language = 'en';
66+ private currentLanguage: LanguageCode = 'en';
77+ private app: App | null = null;
6879 constructor() {
88- // Use browser language as default if supported
99- const browserLang = window.navigator.language.split('-')[0] as Language;
1010+ // Default to browser language initially
1111+ // Will be overridden by Obsidian language or user setting
1212+ const browserLang = window.navigator.language.split('-')[0] as LanguageCode;
1013 if (this.isLanguageSupported(browserLang)) {
1114 this.currentLanguage = browserLang;
1215 }
1316 }
14171515- public setLanguage(lang: Language): void {
1818+ // Store bound event handler for proper cleanup
1919+ private boundCheckForLanguageChanges: (() => void) | null = null;
2020+2121+ /**
2222+ * Initialize the service with the Obsidian app instance
2323+ * This allows getting the app's configured language and listening for changes
2424+ */
2525+ public initializeApp(app: App): void {
2626+ this.app = app;
2727+2828+ // Set up event listener for when Obsidian's language changes
2929+ // Obsidian doesn't directly expose a language change event, but when the app
3030+ // locale changes, many UI elements will be updated and we can listen for those changes
3131+ this.boundCheckForLanguageChanges = this.checkForLanguageChanges.bind(this);
3232+ this.app.workspace.on('layout-change', this.boundCheckForLanguageChanges);
3333+ }
3434+3535+ /**
3636+ * Clean up event listeners when plugin is unloaded
3737+ */
3838+ public cleanup(): void {
3939+ if (this.app && this.boundCheckForLanguageChanges) {
4040+ this.app.workspace.off('layout-change', this.boundCheckForLanguageChanges);
4141+ this.boundCheckForLanguageChanges = null;
4242+ }
4343+ }
4444+4545+ /**
4646+ * Check if Obsidian's language has changed and update if needed
4747+ */
4848+ private checkForLanguageChanges(): void {
4949+ if (!this.app) return;
5050+5151+ const currentObsidianLang = this.getObsidianLanguage();
5252+ if (currentObsidianLang !== this.currentLanguage) {
5353+ this.setLanguage(currentObsidianLang);
5454+ }
5555+ }
5656+5757+ /**
5858+ * Get the ISO code for the currently configured Obsidian app language
5959+ * @returns The language code (defaults to 'en')
6060+ */
6161+ public getObsidianLanguage(): LanguageCode {
6262+ if (!this.app) return 'en';
6363+6464+ // Get language from Obsidian
6565+ // @ts-ignore - getLanguage() exists but isn't in the type definitions
6666+ const obsidianLang = this.app.vault.getConfig('language') || 'en';
6767+6868+ // Handle special cases or normalize language code if needed
6969+ const normalizedLang = this.normalizeLanguageCode(obsidianLang);
7070+7171+ return this.isLanguageSupported(normalizedLang) ? normalizedLang as LanguageCode : 'en';
7272+ }
7373+7474+ /**
7575+ * Normalize language codes to match our supported formats
7676+ */
7777+ private normalizeLanguageCode(langCode: string): string {
7878+ // Handle specific language code mappings
7979+ const mappings: Record<string, string> = {
8080+ 'zh-cn': 'zh',
8181+ 'zh-hans': 'zh',
8282+ 'zh-hant': 'zh-TW',
8383+ 'pt-pt': 'pt',
8484+ 'pt-br': 'pt-BR'
8585+ };
8686+8787+ // Return the mapped value if it exists, otherwise return the original
8888+ return mappings[langCode.toLowerCase()] || langCode;
8989+ }
9090+9191+ /**
9292+ * Set the current language for translations
9393+ */
9494+ public setLanguage(lang: LanguageCode): void {
1695 if (this.isLanguageSupported(lang)) {
1796 this.currentLanguage = lang;
1897 } else {
···21100 }
22101 }
231022424- public getCurrentLanguage(): Language {
103103+ /**
104104+ * Get the current language being used for translations
105105+ */
106106+ public getCurrentLanguage(): LanguageCode {
25107 return this.currentLanguage;
26108 }
27109
+17-5
src/main.ts
···1919 async onload() {
2020 await this.loadSettings();
21212222- // Initialize language from settings
2323- import('./i18n').then(({ i18n }) => {
2424- i18n.setLanguage(this.settings.language);
2525- });
2222+ // Initialize language service with app instance and set language to Obsidian's language
2323+ const { i18n } = await import('./i18n');
2424+ i18n.initializeApp(this.app);
2525+2626+ // Always use Obsidian's language setting
2727+ const obsidianLang = i18n.getObsidianLanguage();
2828+ i18n.setLanguage(obsidianLang);
2929+3030+ // Only update settings if the language has changed
3131+ if (this.settings.language !== obsidianLang) {
3232+ this.settings.language = obsidianLang;
3333+ await this.saveSettings();
3434+ }
26352736 // Create an icon in the left ribbon
2837 const ribbonIconEl = this.addRibbonIcon(
···5160 }
52615362 onunload() {
5454- // Nothing specific to clean up
6363+ // Clean up the translation service's event listeners
6464+ import('./i18n').then(({ i18n }) => {
6565+ i18n.cleanup();
6666+ });
5567 }
56685769 async loadSettings() {