Import AITURK IDE 1.0.0-beta.1 from Hermes 63279301; preserve MIT license

This commit is contained in:
2026-09-05 13:26:46 +03:00
commit 03634b1ca3
11340 changed files with 3442369 additions and 0 deletions
+48
View File
@@ -0,0 +1,48 @@
import { useI18n } from './context'
const en = {
intro: 'Bring your code, question, or idea. Build with AITURK.',
pitch: 'Connect your models with your TurkServis account.', key: 'TurkServis API key',
getKey: 'My account and API keys', noModels: 'No models are available for this key.',
failed: 'Could not connect to TurkServis.',
custom: {
title: 'Custom endpoints', active: 'Active', keySet: 'API key set', use: 'Use', delete: 'Delete endpoint',
empty: 'No custom endpoints', emptyDesc: 'Add an OpenAI-compatible endpoint below.', edit: 'Edit endpoint', add: 'Add endpoint',
name: 'Name', providerId: 'Provider ID', url: 'Endpoint URL', model: 'Default model', context: 'Context', auto: 'Auto',
apiKey: 'API key', keepKey: 'Leave blank to keep current key', optional: 'Optional', newChats: 'Use for new chats',
discover: 'Discover models', test: 'Test', newEndpoint: 'New endpoint', loadFailed: 'Could not load custom endpoints',
saved: 'Custom endpoint saved.', saveFailed: 'Save failed', reachable: 'Endpoint is reachable.', validationFailed: 'Endpoint validation failed.',
activationFailed: 'Activation failed', deleteFailed: 'Delete failed', preset: 'Use TurkServis settings',
found: (n: number) => `Endpoint is reachable. Found ${n} models.`, confirmDelete: (name: string) => `Delete ${name}?`
},
about: 'TurkServis distribution based on Hermes Agent. Upstream: Nous Research, MIT license.',
downloads: 'Official downloads and updates', source: 'Source code and license',
uninstallTitle: 'Uninstall AITURK IDE',
uninstallDescription: 'Use your operating systems application manager to uninstall AITURK IDE. Your separate agent data folder is preserved.'
}
const tr: typeof en = {
intro: 'Kodunuzu, sorunuzu veya fikrinizi paylaşın. AITURK ile birlikte geliştirin.',
pitch: 'TurkServis hesabınızla modellerinize bağlanın.', key: 'TurkServis API anahtarı',
getKey: 'Hesabım ve API anahtarlarım', noModels: 'Bu anahtarla kullanılabilecek model bulunamadı.',
failed: 'TurkServis bağlantısı kurulamadı.',
custom: {
title: 'Özel API bağlantıları', active: 'Etkin', keySet: 'API anahtarı ayarlandı', use: 'Kullan', delete: 'Bağlantıyı sil',
empty: 'Özel bağlantı yok', emptyDesc: 'Aşağıdan OpenAI uyumlu bir API bağlantısı ekleyin.', edit: 'Bağlantıyı düzenle', add: 'Bağlantı ekle',
name: 'Ad', providerId: 'Sağlayıcı kimliği', url: 'API adresi', model: 'Varsayılan model', context: 'Bağlam', auto: 'Otomatik',
apiKey: 'API anahtarı', keepKey: 'Mevcut anahtarı korumak için boş bırakın', optional: 'İsteğe bağlı', newChats: 'Yeni sohbetlerde kullan',
discover: 'Modelleri keşfet', test: 'Sına', newEndpoint: 'Yeni bağlantı', loadFailed: 'Özel bağlantılar yüklenemedi',
saved: 'Özel bağlantı kaydedildi.', saveFailed: 'Kaydedilemedi', reachable: 'API adresine erişiliyor.', validationFailed: 'Bağlantı doğrulanamadı.',
activationFailed: 'Etkinleştirilemedi', deleteFailed: 'Silinemedi', preset: 'TurkServis ayarlarını kullan',
found: n => `API adresine erişiliyor. ${n} model bulundu.`, confirmDelete: name => `${name} silinsin mi?`
},
about: 'Hermes Agent tabanlı TurkServis dağıtımı. Kaynak: Nous Research, MIT lisansı.',
downloads: 'Resmî indirmeler ve güncellemeler', source: 'Kaynak kodu ve lisans',
uninstallTitle: 'AITURK IDEyi kaldır',
uninstallDescription: 'AITURK IDEyi işletim sisteminin uygulama yöneticisinden kaldırın. Windowsta Ayarlar → Uygulamalar → Yüklü uygulamalar yolunu izleyin. Ayrı ajan veri klasörünüz korunur.'
}
export function useAiturkCopy() {
const { locale } = useI18n()
return locale === 'tr' ? tr : en
}
File diff suppressed because it is too large Load Diff
+18
View File
@@ -0,0 +1,18 @@
import { ar } from './ar'
import { en } from './en'
import { ja } from './ja'
import { ru } from './ru'
import { tr } from './tr'
import type { Locale, Translations } from './types'
import { zh } from './zh'
import { zhHant } from './zh-hant'
export const TRANSLATIONS: Record<Locale, Translations> = {
tr,
en,
zh,
'zh-hant': zhHant,
ja,
ar,
ru
}
+250
View File
@@ -0,0 +1,250 @@
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'
import { afterEach, describe, expect, it, vi } from 'vitest'
import type { HermesConfigRecord } from '@/hermes'
import { type I18nConfigClient, I18nProvider, useI18n } from './context'
import type { Locale } from './types'
function LanguageProbe({ target = 'zh' }: { target?: Locale }) {
const { isLoadingConfig, isSavingLocale, locale, saveError, setLocale, t } = useI18n()
return (
<div>
<p data-testid="locale">{locale}</p>
<p data-testid="label">{t.language.label}</p>
<p data-testid="save">{t.common.save}</p>
<p data-testid="loading">{String(isLoadingConfig)}</p>
<p data-testid="saving">{String(isSavingLocale)}</p>
<p data-testid="save-error">{saveError?.message ?? ''}</p>
<button onClick={() => void setLocale(target).catch(() => undefined)} type="button">
switch
</button>
</div>
)
}
describe('I18nProvider', () => {
afterEach(() => {
cleanup()
vi.restoreAllMocks()
})
it('defaults to English without a config client', () => {
render(
<I18nProvider configClient={null}>
<LanguageProbe />
</I18nProvider>
)
expect(screen.getByTestId('locale').textContent).toBe('en')
expect(screen.getByTestId('label').textContent).toBe('Language')
})
it('normalizes an initial locale alias and switches translations', async () => {
render(
<I18nProvider configClient={null} initialLocale="zh-CN">
<LanguageProbe target="en" />
</I18nProvider>
)
expect(screen.getByTestId('locale').textContent).toBe('zh')
expect(screen.getByTestId('label').textContent).toBe('语言')
fireEvent.click(screen.getByRole('button', { name: 'switch' }))
await waitFor(() => expect(screen.getByTestId('locale').textContent).toBe('en'))
expect(screen.getByTestId('label').textContent).toBe('Language')
})
it('loads the initial locale from display.language config', async () => {
const configClient: I18nConfigClient = {
getConfig: vi.fn().mockResolvedValue({ display: { language: 'zh-Hans' } }),
saveConfig: vi.fn()
}
render(
<I18nProvider configClient={configClient}>
<LanguageProbe />
</I18nProvider>
)
await waitFor(() => expect(screen.getByTestId('loading').textContent).toBe('false'))
expect(screen.getByTestId('locale').textContent).toBe('zh')
expect(screen.getByTestId('label').textContent).toBe('语言')
expect(configClient.saveConfig).not.toHaveBeenCalled()
})
it('keeps English usable when config loading fails', async () => {
const configClient: I18nConfigClient = {
getConfig: vi.fn().mockRejectedValue(new Error('config unavailable')),
saveConfig: vi.fn()
}
render(
<I18nProvider configClient={configClient} initialLocale="zh">
<LanguageProbe />
</I18nProvider>
)
await waitFor(() => expect(screen.getByTestId('loading').textContent).toBe('false'))
expect(screen.getByTestId('locale').textContent).toBe('en')
expect(screen.getByTestId('label').textContent).toBe('Language')
expect(configClient.saveConfig).not.toHaveBeenCalled()
})
it('loads zh-hant from display.language config', async () => {
const configClient: I18nConfigClient = {
getConfig: vi.fn().mockResolvedValue({ display: { language: 'zh-TW' } }),
saveConfig: vi.fn()
}
render(
<I18nProvider configClient={configClient} initialLocale="zh">
<LanguageProbe />
</I18nProvider>
)
await waitFor(() => expect(screen.getByTestId('loading').textContent).toBe('false'))
expect(screen.getByTestId('locale').textContent).toBe('zh-hant')
expect(screen.getByTestId('save').textContent).toBe('儲存')
expect(configClient.saveConfig).not.toHaveBeenCalled()
})
it('loads ja from display.language config', async () => {
const configClient: I18nConfigClient = {
getConfig: vi.fn().mockResolvedValue({ display: { language: 'ja-JP' } }),
saveConfig: vi.fn()
}
render(
<I18nProvider configClient={configClient}>
<LanguageProbe />
</I18nProvider>
)
await waitFor(() => expect(screen.getByTestId('loading').textContent).toBe('false'))
expect(screen.getByTestId('locale').textContent).toBe('ja')
expect(screen.getByTestId('save').textContent).toBe('保存')
expect(configClient.saveConfig).not.toHaveBeenCalled()
})
it('does not overwrite unsupported configured languages', async () => {
const configClient: I18nConfigClient = {
getConfig: vi.fn().mockResolvedValue({ display: { language: 'de' } }),
saveConfig: vi.fn()
}
render(
<I18nProvider configClient={configClient} initialLocale="zh">
<LanguageProbe />
</I18nProvider>
)
await waitFor(() => expect(screen.getByTestId('loading').textContent).toBe('false'))
expect(screen.getByTestId('locale').textContent).toBe('en')
expect(screen.getByTestId('label').textContent).toBe('Language')
expect(configClient.saveConfig).not.toHaveBeenCalled()
})
it('reads latest config before saving language and preserves unrelated values', async () => {
const saveConfig = vi.fn().mockResolvedValue({ ok: true })
const latestConfig: HermesConfigRecord = {
display: { language: 'en', skin: 'slate' },
terminal: { cwd: '/new' }
}
const configClient: I18nConfigClient = {
getConfig: vi
.fn()
.mockResolvedValueOnce({ display: { language: 'en', skin: 'mono' }, terminal: { cwd: '/old' } })
.mockResolvedValueOnce(latestConfig),
saveConfig
}
render(
<I18nProvider configClient={configClient}>
<LanguageProbe />
</I18nProvider>
)
await waitFor(() => expect(screen.getByTestId('loading').textContent).toBe('false'))
fireEvent.click(screen.getByRole('button', { name: 'switch' }))
await waitFor(() => expect(saveConfig).toHaveBeenCalledTimes(1))
expect(saveConfig).toHaveBeenCalledWith({
display: { language: 'zh', skin: 'slate' },
terminal: { cwd: '/new' }
})
})
it('saves newly supported locales to display.language', async () => {
const saveConfig = vi.fn().mockResolvedValue({ ok: true })
const configClient: I18nConfigClient = {
getConfig: vi
.fn()
.mockResolvedValueOnce({ display: { language: 'en' } })
.mockResolvedValueOnce({ display: { language: 'en', skin: 'mono' } }),
saveConfig
}
render(
<I18nProvider configClient={configClient}>
<LanguageProbe target="ja" />
</I18nProvider>
)
await waitFor(() => expect(screen.getByTestId('loading').textContent).toBe('false'))
fireEvent.click(screen.getByRole('button', { name: 'switch' }))
await waitFor(() => expect(saveConfig).toHaveBeenCalledTimes(1))
expect(saveConfig).toHaveBeenCalledWith({ display: { language: 'ja', skin: 'mono' } })
expect(screen.getByTestId('locale').textContent).toBe('ja')
})
it('applies RTL direction for Arabic and restores LTR on switch back', async () => {
render(
<I18nProvider configClient={null} initialLocale="ar">
<LanguageProbe target="en" />
</I18nProvider>
)
expect(screen.getByTestId('locale').textContent).toBe('ar')
expect(document.documentElement.dir).toBe('rtl')
expect(document.documentElement.lang).toBe('ar')
fireEvent.click(screen.getByRole('button', { name: 'switch' }))
await waitFor(() => expect(screen.getByTestId('locale').textContent).toBe('en'))
expect(document.documentElement.dir).toBe('ltr')
expect(document.documentElement.lang).toBe('en')
})
it('rolls back the visible locale when saving fails', async () => {
const configClient: I18nConfigClient = {
getConfig: vi.fn().mockResolvedValue({ display: { language: 'en' } }),
saveConfig: vi.fn().mockRejectedValue(new Error('save failed'))
}
render(
<I18nProvider configClient={configClient}>
<LanguageProbe />
</I18nProvider>
)
await waitFor(() => expect(screen.getByTestId('loading').textContent).toBe('false'))
fireEvent.click(screen.getByRole('button', { name: 'switch' }))
await waitFor(() => expect(screen.getByTestId('save-error').textContent).toBe('save failed'))
expect(screen.getByTestId('locale').textContent).toBe('en')
expect(screen.getByTestId('label').textContent).toBe('Language')
})
})
+196
View File
@@ -0,0 +1,196 @@
import { createContext, type ReactNode, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
import { getHermesConfigRecord, type HermesConfigRecord, saveHermesConfig } from '@/hermes'
import { TRANSLATIONS } from './catalog'
import { DEFAULT_LOCALE, localeConfigValue, normalizeLocale } from './languages'
import { setRuntimeI18nLocale } from './runtime'
import type { Locale, Translations } from './types'
export { LOCALE_META } from './languages'
export interface I18nConfigClient {
getConfig: () => Promise<HermesConfigRecord>
saveConfig: (config: HermesConfigRecord) => Promise<{ ok: boolean }>
}
const defaultConfigClient: I18nConfigClient = {
getConfig: () => {
if (typeof window === 'undefined' || !window.hermesDesktop?.api) {
return Promise.resolve({})
}
return getHermesConfigRecord()
},
saveConfig: config => {
if (typeof window === 'undefined' || !window.hermesDesktop?.api) {
return Promise.resolve({ ok: true })
}
return saveHermesConfig(config)
}
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value)
}
export function getConfigDisplayLanguage(config: HermesConfigRecord): unknown {
return isRecord(config.display) ? config.display.language : undefined
}
export function withConfigDisplayLanguage(config: HermesConfigRecord, locale: Locale): HermesConfigRecord {
const display = isRecord(config.display) ? config.display : {}
return {
...config,
display: {
...display,
language: localeConfigValue(locale)
}
}
}
function toError(error: unknown): Error {
return error instanceof Error ? error : new Error(String(error))
}
const RTL_LOCALES = new Set<Locale>(['ar'])
function applyDocumentLocale(locale: Locale) {
if (typeof document === 'undefined') {
return
}
document.documentElement.lang = locale
document.documentElement.dir = RTL_LOCALES.has(locale) ? 'rtl' : 'ltr'
}
export interface I18nContextValue {
configLoadError: Error | null
isLoadingConfig: boolean
isSavingLocale: boolean
locale: Locale
saveError: Error | null
setLocale: (next: Locale) => Promise<void>
t: Translations
}
const I18nContext = createContext<I18nContextValue>({
configLoadError: null,
isLoadingConfig: false,
isSavingLocale: false,
locale: DEFAULT_LOCALE,
saveError: null,
setLocale: async () => {},
t: TRANSLATIONS[DEFAULT_LOCALE]
})
export interface I18nProviderProps {
children: ReactNode
configClient?: I18nConfigClient | null
initialLocale?: unknown
}
export function I18nProvider({ children, configClient = defaultConfigClient, initialLocale }: I18nProviderProps) {
const [locale, setLocaleState] = useState<Locale>(() => normalizeLocale(initialLocale))
const [isLoadingConfig, setIsLoadingConfig] = useState(false)
const [isSavingLocale, setIsSavingLocale] = useState(false)
const [configLoadError, setConfigLoadError] = useState<Error | null>(null)
const [saveError, setSaveError] = useState<Error | null>(null)
const localeRef = useRef(locale)
// eslint-disable-next-line no-restricted-syntax -- legitimate non-atom ref write (see eslint rule comment)
useEffect(() => {
localeRef.current = locale
setRuntimeI18nLocale(locale)
applyDocumentLocale(locale)
}, [locale])
useEffect(() => {
if (!configClient) {
return
}
let cancelled = false
setIsLoadingConfig(true)
setConfigLoadError(null)
configClient
.getConfig()
.then(config => {
if (!cancelled) {
setLocaleState(normalizeLocale(getConfigDisplayLanguage(config)))
}
})
.catch(error => {
if (!cancelled) {
setConfigLoadError(toError(error))
setLocaleState(DEFAULT_LOCALE)
}
})
.finally(() => {
if (!cancelled) {
setIsLoadingConfig(false)
}
})
return () => {
cancelled = true
}
}, [configClient, initialLocale])
const setLocale = useCallback(
async (next: Locale) => {
const previousLocale = localeRef.current
setSaveError(null)
setLocaleState(next)
if (!configClient) {
return
}
setIsSavingLocale(true)
try {
const latestConfig = await configClient.getConfig()
const result = await configClient.saveConfig(withConfigDisplayLanguage(latestConfig, next))
if (!result.ok) {
throw new Error('Failed to save language')
}
} catch (error) {
const nextError = toError(error)
setLocaleState(previousLocale)
setSaveError(nextError)
throw nextError
} finally {
setIsSavingLocale(false)
}
},
[configClient]
)
const value = useMemo<I18nContextValue>(
() => ({
configLoadError,
isLoadingConfig,
isSavingLocale,
locale,
saveError,
setLocale,
t: TRANSLATIONS[locale]
}),
[configLoadError, isLoadingConfig, isSavingLocale, locale, saveError, setLocale]
)
return <I18nContext.Provider value={value}>{children}</I18nContext.Provider>
}
export function useI18n(): I18nContextValue {
return useContext(I18nContext)
}
+41
View File
@@ -0,0 +1,41 @@
import { en } from './en'
import type { Translations } from './types'
type TranslationOverride<T> = T extends (...args: never[]) => string
? T
: T extends readonly unknown[]
? T
: T extends string
? string
: T extends object
? { [K in keyof T]?: TranslationOverride<T[K]> }
: T
export type TranslationOverrides = TranslationOverride<Translations>
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value)
}
function mergeTranslations<T>(base: T, overrides: TranslationOverride<T> | undefined): T {
if (!isRecord(base) || !isRecord(overrides)) {
return (overrides ?? base) as T
}
const result: Record<string, unknown> = { ...base }
for (const [key, value] of Object.entries(overrides)) {
if (value === undefined) {
continue
}
const baseValue = result[key]
result[key] = isRecord(baseValue) && isRecord(value) ? mergeTranslations(baseValue, value) : value
}
return result as T
}
export function defineLocale(overrides: TranslationOverrides): Translations {
return mergeTranslations<Translations>(en, overrides)
}
File diff suppressed because it is too large Load Diff
+31
View File
@@ -0,0 +1,31 @@
export { TRANSLATIONS } from './catalog'
export {
getConfigDisplayLanguage,
type I18nConfigClient,
type I18nContextValue,
I18nProvider,
LOCALE_META,
useI18n,
withConfigDisplayLanguage
} from './context'
export {
DEFAULT_LOCALE,
isLocale,
isSupportedLocaleValue,
LOCALE_OPTIONS,
localeConfigValue,
normalizeLocale
} from './languages'
export {
createPluginI18n,
type PluginI18n,
type PluginLocaleBundles,
type PluginMessages,
type PluginMessageValue,
type PluginTranslate,
registerPluginLocales,
translatePlugin,
usePluginI18n
} from './plugin-i18n'
export { setRuntimeI18nLocale, translateNow } from './runtime'
export type { Locale, ToolTitleKey, Translations } from './types'
File diff suppressed because it is too large Load Diff
+55
View File
@@ -0,0 +1,55 @@
import { describe, expect, it } from 'vitest'
import { DEFAULT_LOCALE, isLocale, isSupportedLocaleValue, localeConfigValue, normalizeLocale } from './languages'
describe('desktop i18n languages', () => {
it('normalizes supported locale aliases', () => {
expect(normalizeLocale('en')).toBe('en')
expect(normalizeLocale('EN-US')).toBe('en')
expect(normalizeLocale('zh')).toBe('zh')
expect(normalizeLocale('zh-CN')).toBe('zh')
expect(normalizeLocale('zh-Hans')).toBe('zh')
expect(normalizeLocale(' zh_hans_cn ')).toBe('zh')
expect(normalizeLocale('zh-Hant')).toBe('zh-hant')
expect(normalizeLocale('zh-TW')).toBe('zh-hant')
expect(normalizeLocale('zh_HK')).toBe('zh-hant')
expect(normalizeLocale('ja')).toBe('ja')
expect(normalizeLocale('ja-JP')).toBe('ja')
expect(normalizeLocale('ar')).toBe('ar')
expect(normalizeLocale('AR-SA')).toBe('ar')
expect(normalizeLocale(' ar_eg ')).toBe('ar')
expect(normalizeLocale('ru')).toBe('ru')
expect(normalizeLocale('RU-RU')).toBe('ru')
expect(normalizeLocale(' ru_ru ')).toBe('ru')
expect(normalizeLocale('Русский')).toBe('ru')
})
it('falls back to English for empty or unsupported values', () => {
expect(normalizeLocale(null)).toBe(DEFAULT_LOCALE)
expect(normalizeLocale('')).toBe(DEFAULT_LOCALE)
expect(normalizeLocale('de')).toBe(DEFAULT_LOCALE)
})
it('distinguishes exact locale ids from supported config aliases', () => {
expect(isSupportedLocaleValue('zh-CN')).toBe(true)
expect(isSupportedLocaleValue('zh-TW')).toBe(true)
expect(isSupportedLocaleValue('ja-JP')).toBe(true)
expect(isSupportedLocaleValue('ru-RU')).toBe(true)
expect(isSupportedLocaleValue('de')).toBe(false)
expect(isLocale('zh-CN')).toBe(false)
expect(isLocale('zh')).toBe(true)
expect(isLocale('zh-hant')).toBe(true)
expect(isLocale('ja')).toBe(true)
expect(isLocale('ar')).toBe(true)
expect(isLocale('ru')).toBe(true)
})
it('returns the persisted config value for supported locales', () => {
expect(localeConfigValue('en')).toBe('en')
expect(localeConfigValue('zh')).toBe('zh')
expect(localeConfigValue('zh-hant')).toBe('zh-hant')
expect(localeConfigValue('ja')).toBe('ja')
expect(localeConfigValue('ar')).toBe('ar')
expect(localeConfigValue('ru')).toBe('ru')
})
})
+127
View File
@@ -0,0 +1,127 @@
import { normalize } from '@/lib/text'
import type { Locale } from './types'
export const DEFAULT_LOCALE: Locale = 'tr'
// Plugin bundles may not contain Turkish yet. Their fallback remains English.
export const FALLBACK_LOCALE: Locale = 'en'
export const LOCALE_OPTIONS = [
{ id: 'tr', name: 'Türkçe', englishName: 'Turkish', configValue: 'tr' },
{
id: 'en',
name: 'English',
englishName: 'English',
configValue: 'en'
},
{
id: 'zh',
name: '简体中文',
englishName: 'Simplified Chinese',
configValue: 'zh'
},
{
id: 'zh-hant',
name: '繁體中文',
englishName: 'Traditional Chinese',
configValue: 'zh-hant'
},
{
id: 'ja',
name: '日本語',
englishName: 'Japanese',
configValue: 'ja'
},
{
id: 'ar',
name: 'العربية',
englishName: 'Arabic',
configValue: 'ar'
},
{
id: 'ru',
name: 'Русский',
englishName: 'Russian',
configValue: 'ru'
}
] as const satisfies readonly { configValue: string; englishName: string; id: Locale; name: string }[]
// `name` is the endonym (native name) shown in the picker so users recognize
// their language regardless of the current UI language. No country flags:
// languages are not countries. `englishName` is search-only (not shown) so an
// English speaker can type "japanese"/"traditional" to filter the list.
export const LOCALE_META: Record<Locale, { name: string; englishName: string }> = Object.fromEntries(
LOCALE_OPTIONS.map(locale => [locale.id, { name: locale.name, englishName: locale.englishName }])
) as Record<Locale, { name: string; englishName: string }>
const LOCALE_ALIASES: Record<string, Locale> = {
tr: 'tr',
'tr-tr': 'tr',
tr_tr: 'tr',
turkish: 'tr',
'türkçe': 'tr',
turkce: 'tr',
en: 'en',
'en-us': 'en',
en_us: 'en',
zh: 'zh',
'zh-cn': 'zh',
zh_cn: 'zh',
'zh-hans': 'zh',
zh_hans: 'zh',
'zh-hans-cn': 'zh',
zh_hans_cn: 'zh',
'zh-tw': 'zh-hant',
zh_tw: 'zh-hant',
'zh-hk': 'zh-hant',
zh_hk: 'zh-hant',
'zh-mo': 'zh-hant',
zh_mo: 'zh-hant',
'zh-hant': 'zh-hant',
zh_hant: 'zh-hant',
'zh-hant-tw': 'zh-hant',
zh_hant_tw: 'zh-hant',
'zh-hant-hk': 'zh-hant',
zh_hant_hk: 'zh-hant',
ja: 'ja',
'ja-jp': 'ja',
ja_jp: 'ja',
ar: 'ar',
'ar-sa': 'ar',
ar_sa: 'ar',
'ar-ae': 'ar',
ar_ae: 'ar',
'ar-eg': 'ar',
ar_eg: 'ar',
arabic: 'ar',
العربية: 'ar',
ru: 'ru',
'ru-ru': 'ru',
ru_ru: 'ru',
'ru-by': 'ru',
'ru-kz': 'ru',
russian: 'ru',
'russian-russian': 'ru',
русский: 'ru',
руский: 'ru'
}
export function isLocale(value: unknown): value is Locale {
return typeof value === 'string' && LOCALE_OPTIONS.some(locale => locale.id === value)
}
export function normalizeLocale(value: unknown): Locale {
if (typeof value !== 'string') {
return DEFAULT_LOCALE
}
return LOCALE_ALIASES[normalize(value)] ?? DEFAULT_LOCALE
}
export function isSupportedLocaleValue(value: unknown): boolean {
return typeof value === 'string' && LOCALE_ALIASES[normalize(value)] != null
}
export function localeConfigValue(locale: Locale): string {
return LOCALE_OPTIONS.find(item => item.id === locale)?.configValue ?? DEFAULT_LOCALE
}
+126
View File
@@ -0,0 +1,126 @@
import { act, cleanup, fireEvent, render, screen } from '@testing-library/react'
import { afterEach, describe, expect, it } from 'vitest'
import { I18nProvider, useI18n } from './context'
import { createPluginI18n, registerPluginLocales, translatePlugin, usePluginI18n } from './plugin-i18n'
import { setRuntimeI18nLocale } from './runtime'
const noopTrack = (dispose: () => void) => dispose
afterEach(() => {
cleanup()
setRuntimeI18nLocale('en')
})
describe('plugin locale registry', () => {
it('resolves the active locale, falling back to English then the raw key', () => {
const dispose = registerPluginLocales('cost', {
en: { panel: { title: 'Cost' }, spent: (n: number) => `$${n} spent` },
ja: { panel: { title: 'コスト' } }
})
expect(translatePlugin('cost', 'ja', 'panel.title', [])).toBe('コスト')
// Missing in ja → English.
expect(translatePlugin('cost', 'ja', 'spent', [7])).toBe('$7 spent')
// Missing everywhere → the key itself.
expect(translatePlugin('cost', 'ja', 'nope', [])).toBe('nope')
dispose()
})
it('scopes bundles per plugin — no cross-read', () => {
const a = registerPluginLocales('a', { en: { hi: 'from a' } })
const b = registerPluginLocales('b', { en: { hi: 'from b' } })
expect(translatePlugin('a', 'en', 'hi', [])).toBe('from a')
expect(translatePlugin('b', 'en', 'hi', [])).toBe('from b')
// An unknown plugin resolves to the key.
expect(translatePlugin('c', 'en', 'hi', [])).toBe('hi')
a()
b()
})
it('merges repeated registrations and drops everything on dispose', () => {
const one = registerPluginLocales('merge', { en: { a: 'A' } })
const two = registerPluginLocales('merge', { en: { b: 'B' }, ja: { a: 'あ' } })
expect(translatePlugin('merge', 'en', 'a', [])).toBe('A')
expect(translatePlugin('merge', 'en', 'b', [])).toBe('B')
expect(translatePlugin('merge', 'ja', 'a', [])).toBe('あ')
one()
two()
expect(translatePlugin('merge', 'en', 'a', [])).toBe('a')
})
it('ctx.i18n.t reads the app runtime locale', () => {
const i18n = createPluginI18n('runtime-plugin', noopTrack)
i18n.register({ en: { greet: 'hello' }, ja: { greet: 'こんにちは' } })
expect(i18n.t('greet')).toBe('hello')
setRuntimeI18nLocale('ja')
expect(i18n.t('greet')).toBe('こんにちは')
})
})
function Probe({ pluginId }: { pluginId: string }) {
const t = usePluginI18n(pluginId)
return <p data-testid="copy">{t('greet')}</p>
}
function SwitchToJa() {
const { setLocale } = useI18n()
return (
<button onClick={() => void setLocale('ja')} type="button">
to ja
</button>
)
}
describe('usePluginI18n', () => {
it('re-renders on a locale switch', () => {
const dispose = registerPluginLocales('hooked', {
en: { greet: 'hello' },
ja: { greet: 'こんにちは' }
})
render(
<I18nProvider configClient={null}>
<SwitchToJa />
<Probe pluginId="hooked" />
</I18nProvider>
)
expect(screen.getByTestId('copy').textContent).toBe('hello')
fireEvent.click(screen.getByRole('button'))
expect(screen.getByTestId('copy').textContent).toBe('こんにちは')
dispose()
})
it('picks up a bundle registered after mount', () => {
render(
<I18nProvider configClient={null}>
<Probe pluginId="late" />
</I18nProvider>
)
expect(screen.getByTestId('copy').textContent).toBe('greet')
let dispose = () => {}
act(() => {
dispose = registerPluginLocales('late', { en: { greet: 'landed' } })
})
expect(screen.getByTestId('copy').textContent).toBe('landed')
dispose()
})
})
+118
View File
@@ -0,0 +1,118 @@
/**
* Plugin-scoped i18n — the `ctx.storage` analog for locale bundles. A plugin
* ships its own strings and registers them under its id; it never edits core
* `en.ts`. Resolution mirrors the app translator: active locale → the plugin's
* own `en` bundle → the key itself. The active locale is always the app's
* (`display.language`) — plugins follow the user's choice, they don't own it.
*
* Two consumers, same shape as core (`useI18n` / `translateNow`):
* - `usePluginI18n(id)` — reactive translator for React UI (re-renders on a
* locale switch or a late bundle registration);
* - `ctx.i18n.t` — module-level translator for handlers/stores (non-reactive).
*/
import { useStore } from '@nanostores/react'
import { atom } from 'nanostores'
import { useCallback } from 'react'
import { useI18n } from './context'
import { getRuntimeI18nLocale, translateFrom } from './runtime'
import type { Locale } from './types'
/** A leaf message: a literal or an interpolator (`n => `${n} left``). */
export type PluginMessageValue = string | ((...args: never[]) => string)
/** A plugin's messages for one locale — nested trees allowed, addressed by
* dot-path (`panel.title`). */
export interface PluginMessages {
[key: string]: PluginMessages | PluginMessageValue
}
/** Locale → messages. Keyed by the app's locales so autocomplete guides you;
* a bundle for a locale the app can't select is simply never resolved. */
export type PluginLocaleBundles = Partial<Record<Locale, PluginMessages>>
/** Resolve `key` for this plugin against `args`; falls back to English, then
* the raw key. */
export type PluginTranslate = (key: string, ...args: unknown[]) => string
export interface PluginI18n {
/** Merge locale bundles for this plugin (call once at `register`). Returns a
* disposer that drops the plugin's bundles on unload/reload. */
register: (bundles: PluginLocaleBundles) => () => void
/** Module-level translator against the app's active locale (mirrors
* `translateNow`). Non-reactive — in React prefer `usePluginI18n`. */
t: PluginTranslate
}
const registry = new Map<string, Map<Locale, PluginMessages>>()
/** Bumps whenever a plugin's bundles change, so React translators re-render on
* a registration that lands after first paint. */
const $version = atom(0)
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value)
}
function mergeMessages(base: PluginMessages, overrides: PluginMessages): PluginMessages {
const result: PluginMessages = { ...base }
for (const [key, value] of Object.entries(overrides)) {
const prev = result[key]
result[key] = isRecord(prev) && isRecord(value) ? mergeMessages(prev, value) : value
}
return result
}
export function registerPluginLocales(pluginId: string, bundles: PluginLocaleBundles): () => void {
const byLocale = registry.get(pluginId) ?? new Map<Locale, PluginMessages>()
registry.set(pluginId, byLocale)
for (const [locale, messages] of Object.entries(bundles) as [Locale, PluginMessages | undefined][]) {
if (!messages) {
continue
}
const prev = byLocale.get(locale)
byLocale.set(locale, prev ? mergeMessages(prev, messages) : messages)
}
$version.set($version.get() + 1)
return () => {
registry.delete(pluginId)
$version.set($version.get() + 1)
}
}
export function translatePlugin(pluginId: string, locale: Locale, key: string, args: unknown[]): string {
return translateFrom(l => registry.get(pluginId)?.get(l), locale, key, args)
}
/** Build the `ctx.i18n` door for a plugin. `track` records the disposer so the
* loader tears bundles down on unload (same lifecycle as `register`/`socket`). */
export function createPluginI18n(pluginId: string, track: (dispose: () => void) => () => void): PluginI18n {
return {
register: bundles => track(registerPluginLocales(pluginId, bundles)),
t: (key, ...args) => translatePlugin(pluginId, getRuntimeI18nLocale(), key, args)
}
}
/** Reactive scoped translator for React UI. Re-renders on a locale switch or a
* late bundle registration. Pass your plugin id (your default export's `id`). */
export function usePluginI18n(pluginId: string): PluginTranslate {
const { locale } = useI18n()
const version = useStore($version)
// `version` is the registry's change token and must key the translator's
// identity: memoized consumers (React.memo, React Compiler output) cache
// render slices on `t` itself, so a stable `t` over a mutated registry
// serves stale strings after a late bundle registration.
return useCallback(
(key: string, ...args: unknown[]) => translatePlugin(pluginId, locale, key, args),
// eslint-disable-next-line react-hooks/exhaustive-deps
[pluginId, locale, version]
)
}
File diff suppressed because it is too large Load Diff
+81
View File
@@ -0,0 +1,81 @@
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
import { fieldCopyForSchemaKey } from '@/app/settings/field-copy'
import { TRANSLATIONS } from './catalog'
import { setRuntimeI18nLocale, translateNow } from './runtime'
import { zh } from './zh'
describe('desktop i18n runtime translator', () => {
beforeEach(() => {
setRuntimeI18nLocale('en')
})
afterEach(() => {
setRuntimeI18nLocale('en')
})
it('translates string paths for the active runtime locale', () => {
setRuntimeI18nLocale('zh')
expect(translateNow('boot.ready')).toBe('Hermes 桌面版已就绪')
expect(translateNow('notifications.voice.noSpeechDetected')).toBe('没有检测到语音')
expect(translateNow('composer.lookupNoMatches')).toBe('没有匹配项。')
expect(translateNow('assistant.tool.statusRecovered')).toBe('已恢复')
})
it('passes arguments to function translations', () => {
expect(translateNow('notifications.updateReadyMessage', 2)).toBe('2 new changes available.')
})
it('translates migrated overlap keys for newly supported locales', () => {
setRuntimeI18nLocale('ja')
expect(translateNow('common.save')).toBe('保存')
setRuntimeI18nLocale('zh-hant')
expect(translateNow('cron.promptPlaceholder')).toBe('代理每次執行時應做什麼?')
})
it('translates settings copy for newly supported locales', () => {
setRuntimeI18nLocale('ja')
expect(translateNow('settings.appearance.title')).toBe('外観')
expect(translateNow('settings.nav.providers')).toBe('プロバイダー')
setRuntimeI18nLocale('zh-hant')
expect(translateNow('settings.appearance.title')).toBe('外觀')
expect(translateNow('settings.nav.providerApiKeys')).toBe('API 金鑰')
setRuntimeI18nLocale('ar')
expect(translateNow('settings.appearance.reasoningCollapsedTitle')).toBe('طي التفكير افتراضيًا')
expect(translateNow('settings.appearance.reasoningCollapsedDesc')).toBe(
'أبقِ التفكير المتدفق متاحًا دون توسيعه حتى تفتحه.'
)
})
it('keeps translated settings field copy addressable from schema keys', () => {
const field = ['display', 'show_reasoning'].join('.')
expect(fieldCopyForSchemaKey(zh.settings.fieldLabels, field)).toBe('推理过程块')
expect(fieldCopyForSchemaKey(zh.settings.fieldDescriptions, field)).toBe('当后端提供推理内容时予以显示。')
})
it('falls back to English when the active locale cannot resolve a key', () => {
const boot = TRANSLATIONS.ja.boot as { ready?: string }
const originalReady = boot.ready
try {
boot.ready = undefined
setRuntimeI18nLocale('ja')
expect(translateNow('boot.ready')).toBe('Hermes Desktop is ready')
} finally {
boot.ready = originalReady
}
})
it('returns the key when no locale can resolve a path', () => {
setRuntimeI18nLocale('zh')
expect(translateNow('missing.path')).toBe('missing.path')
})
})
+66
View File
@@ -0,0 +1,66 @@
import { TRANSLATIONS } from './catalog'
import { DEFAULT_LOCALE, FALLBACK_LOCALE } from './languages'
import type { Locale } from './types'
let runtimeLocale: Locale = DEFAULT_LOCALE
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value)
}
/** Walk a dot-path (`a.b.c`) into a nested message tree. */
function resolvePath(source: unknown, key: string): unknown {
return key.split('.').reduce<unknown>((current, part) => (isRecord(current) ? current[part] : undefined), source)
}
/** A string is returned as-is, a function is called with `args`, else `null`. */
function render(value: unknown, args: unknown[]): null | string {
if (typeof value === 'string') {
return value
}
if (typeof value === 'function') {
return (value as (...args: unknown[]) => string)(...args)
}
return null
}
/** The active DEFAULT key resolution every translator shares. `source`
* yields a message tree per locale the app catalog, or a plugin's bundles. */
export function translateFrom(
source: (locale: Locale) => unknown,
locale: Locale,
key: string,
args: unknown[]
): string {
const active = render(resolvePath(source(locale), key), args)
if (active !== null) {
return active
}
if (locale !== FALLBACK_LOCALE) {
const fallback = render(resolvePath(source(FALLBACK_LOCALE), key), args)
if (fallback !== null) {
return fallback
}
}
return key
}
export function setRuntimeI18nLocale(locale: Locale) {
runtimeLocale = locale
}
/** The locale module-level translators resolve against (the app's active
* `display.language`). Plugin `ctx.i18n.t` reads this too. */
export function getRuntimeI18nLocale(): Locale {
return runtimeLocale
}
export function translateNow(key: string, ...args: unknown[]): string {
return translateFrom(locale => TRANSLATIONS[locale], runtimeLocale, key, args)
}
+26
View File
@@ -0,0 +1,26 @@
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import { describe, expect, it, vi } from 'vitest'
import { I18nProvider, useI18n } from './context'
import { normalizeLocale } from './languages'
function Probe() {
const { locale, setLocale, t } = useI18n()
return <button onClick={() => void setLocale(locale === 'tr' ? 'en' : 'tr')}>{t.common.save}</button>
}
describe('Turkish language pack', () => {
it('accepts Turkish aliases', () => {
for (const value of ['tr', 'TR-TR', 'tr_TR', 'Türkçe', 'turkce', 'Turkish']) expect(normalizeLocale(value)).toBe('tr')
})
it('starts in Turkish and persists a language change without losing sibling settings', async () => {
const saveConfig = vi.fn().mockResolvedValue({ ok: true })
const config = { display: { density: 'compact' }, model: { default: 'account/model' } }
render(<I18nProvider configClient={{ getConfig: vi.fn().mockResolvedValue(config), saveConfig }}><Probe /></I18nProvider>)
expect(screen.getByRole('button', { name: 'Kaydet' })).toBeTruthy()
await waitFor(() => expect(document.documentElement.lang).toBe('tr'))
fireEvent.click(screen.getByRole('button', { name: 'Kaydet' }))
await waitFor(() => expect(screen.getByRole('button', { name: 'Save' })).toBeTruthy())
expect(saveConfig).toHaveBeenCalledWith({ ...config, display: { ...config.display, language: 'en' } })
})
})
+582
View File
@@ -0,0 +1,582 @@
import { defineLocale } from './define-locale'
// AITURK Turkish translation. Technical identifiers and provider trademarks
// retain their original spelling so they match configuration and API values.
export const tr = defineLocale({
zones: {
bots: 'Botlar', showTabStrip: 'Sekmeleri göster', hideTabStrip: 'Sekmeleri gizle',
showStripTab: title => `${title} göster`, hideStripTab: title => `${title} gizle`,
lastTabKeptTitle: 'Son sekme korunur', lastTabKeptBody: 'Bu alanda en az bir görünür sekme olmalıdır. Önce başka bir sekme gösterin veya yan çubuğu daraltın.',
toggleStripTab: title => `${title} sekmesini aç/kapat`, minimize: 'Küçült', restore: 'Geri yükle',
closeRunningTitle: 'Çalışan sekme kapatılsın mı?', closeRunningBody: 'Bu sohbet hâlâ çalışıyor veya yanıtınızı bekliyor. Sekmeyi kapatınca ilerleme korunur; yan çubuktan yeniden açabilirsiniz.',
closeRunningConfirm: 'Sekmeyi kapat', reload: 'Yeniden yükle', closeOthers: 'Diğerlerini kapat', closeToRight: 'Sağdakileri kapat', closeAll: 'Tümünü kapat',
newSessionTab: 'Yeni oturum sekmesi', newTab: 'Yeni sekme', pluginDisabled: id => `${id}” eklentisi devre dışı`,
pluginDisabledBody: 'Paneli geri getirmek için Ayarlar → Eklentiler bölümünden etkinleştirin.', missingPane: id => `Eksik panel: ${id}`,
editTitle: 'Düzenler', editHint: 'Düzen seçin veya panelleri alanlar arasında sürükleyin.', reset: 'Sıfırla', templates: 'Şablonlar', custom: 'Özel',
newGridLayout: 'Yeni ızgara düzeni', saveCurrentAs: 'Mevcut düzeni şablon olarak kaydet', nameLayoutPlaceholder: 'Düzene ad verin…',
deletePreset: name => `${name} sil`, zoneEditorTitle: 'Alan düzenleyici', editorHintPre: 'Bölmek için tıklayın · ',
editorHintPost: ' çizgiyi çevirir · birleştirmek için alanlar arasında sürükleyin · boyutlandırmak için ortak kenarı sürükleyin',
templateColumns: 'Sütunlar', templateRows: 'Satırlar', templateGrid: 'Izgara', templatePriority: 'Öncelik', zoneTag: n => `Alan ${n}`,
mergeZones: n => `${n} alanı birleştir`, customZoneName: n => `Özel ${n} alan`, layoutNamePlaceholder: name => `Düzen adı (${name})`,
saveApply: 'Kaydet ve uygula', notExpressible: 'Bu iç içe düzen henüz desteklenmiyor', zoneCount: n => `${n} alan`, tabCount: n => `${n} sekme`
},
common: {
apply: 'Uygula', back: 'Geri', save: 'Kaydet', saving: 'Kaydediliyor…',
cancel: 'İptal', change: 'Değiştir', choose: 'Seç', clear: 'Temizle', close: 'Kapat',
collapse: 'Daralt', confirm: 'Onayla', connect: 'Bağlan', connecting: 'Bağlanıyor',
continue: 'Devam et', copied: 'Kopyalandı', copy: 'Kopyala', copyFailed: 'Kopyalanamadı',
delete: 'Sil', docs: 'Belgeler', done: 'Tamam', error: 'Hata', expand: 'Genişlet',
failed: 'Başarısız', formatJson: 'JSON biçimlendir', free: 'Ücretsiz', loading: 'Yükleniyor…',
notSet: 'Ayarlanmadı', refresh: 'Yenile', remove: 'Kaldır', replace: 'Değiştir', retry: 'Yeniden dene',
run: 'Çalıştır', send: 'Gönder', set: 'Ayarla', skip: 'Atla', update: 'Güncelle',
tryHint: term => `${term}” deneyin`, on: 'Açık', off: 'Kapalı'
},
fileMenu: {
revealFinder: 'Finderda göster', revealExplorer: 'Dosya Gezgininde göster',
revealFileManager: 'İçeren klasörü aç', revealInSidebar: 'Dosya ağacında göster',
copyPath: 'Yolu kopyala', copyRelativePath: 'Göreli yolu kopyala', download: 'İndir',
downloadSaved: 'Kaydedildi', downloadFailed: 'İndirilemedi', rename: 'Yeniden adlandır…',
delete: 'Sil', renameTitle: 'Yeniden adlandır', renameLabel: 'Yeni ad',
deleteTitle: name => `${name} silinsin mi?`,
deleteBody: 'Çöp Kutusuna taşınacak; buradan geri yükleyebilirsiniz.', pathCopied: 'Yol kopyalandı'
},
boot: {
ready: 'Hazır', desktopBootFailedWithMessage: message => `AITURK IDE başlatılamadı: ${message}`,
steps: {
connectingGateway: 'Ağ geçidine bağlanılıyor', loadingSettings: 'Ayarlar yükleniyor',
loadingSessions: 'Oturumlar yükleniyor', retryingRemoteBackend: 'Uzak sunucu yeniden deneniyor',
startingDesktopConnection: 'Masaüstü bağlantısı başlatılıyor', startingHermesDesktop: 'AITURK IDE başlatılıyor'
},
errors: {
backgroundExited: 'Arka plan işlemi kapandı', backgroundExitedDuringStartup: 'Arka plan işlemi başlatılırken kapandı',
backendStopped: 'Sunucu durdu', desktopBootFailed: 'AITURK IDE başlatılamadı',
gatewayConnectionLost: 'Ağ geçidi bağlantısı kesildi',
gatewayConnectionLostDetail: 'Bağlantı arka planda yeniden deneniyor. Okumaya ve taslak yazmaya devam edebilirsiniz. Sorun sürerse ağ geçidi ayarlarını açın.',
gatewaySignInRequired: 'Ağ geçidinde oturum açmanız gerekiyor', ipcBridgeUnavailable: 'Masaüstü IPC köprüsüne erişilemiyor.'
},
failure: {
title: 'AITURK IDE başlatılamadı',
description: 'Arka plan ağ geçidi başlatılamadı. Aşağıdaki kurtarma adımlarını deneyin. Bu işlemler sohbetlerinizi veya ayarlarınızı silmez.',
remoteTitle: 'Uzak ağ geçidinde oturum açmanız gerekiyor',
remoteDescription: 'Uzak ağ geçidi oturumunuzun süresi doldu. Yeniden bağlanmak için giriş yapın. Sohbetleriniz ve ayarlarınız korunur.',
retry: 'Yeniden dene', repairInstall: 'Kurulumu onar', useLocalGateway: 'Yerel ağ geçidini kullan',
gatewaySettings: 'Ağ geçidi ayarları', back: 'Geri', openLogs: 'Günlükleri aç',
repairHint: 'Onarım, kurulum programını yeniden çalıştırır. Yeni bir bilgisayarda birkaç dakika sürebilir.',
remoteSignInHint: label => `Kayıtlı tarayıcı oturumundan çıkılır ve ${label} açılır. Yerel sunucuya geçmek için yerel ağ geçidini seçin.`,
signOutAndSignIn: 'Çıkış yap ve yeniden giriş yap',
remoteFailureHint: 'Ağ geçidi ayarlarındaki adresi ve oturumu kontrol edin veya yerel ağ geçidine geçin.',
cloudDownTitle: 'Nous Cloud ajanı çalışmıyor',
cloudDownDescription: 'Bu bağlantının kullandığı Nous Cloud ajanı sunucu hatası döndürüyor. Buradan yeniden başlatılamaz. Durumunu kontrol edin, yerel ağ geçidine geçin veya destek alın.',
cloudDownHint: 'Aşağıdaki düğmeler Nous Portal durum sayfasını ve Discord desteğini açar.',
cloudDownCheckPortal: 'Portal durumunu kontrol et', cloudDownDiscord: 'Discorddan yardım al',
hideRecentLogs: 'Son günlükleri gizle', showRecentLogs: 'Son günlükleri göster',
signedInTitle: 'Oturum açıldı', signedInMessage: 'Uzak ağ geçidine yeniden bağlanılıyor…',
signInIncompleteTitle: 'Giriş tamamlanmadı', signInIncompleteMessage: 'Kimlik doğrulama bitmeden giriş penceresi kapandı.',
signInFailed: 'Giriş başarısız', signInToRemoteGateway: 'Uzak ağ geçidinde oturum aç',
signInWithProvider: provider => `${provider} ile giriş yap`, identityProvider: 'kimlik sağlayıcınız'
}
},
notifications: {
region: 'Bildirimler', hide: 'Gizle', show: 'Göster', more: count => `${count} bildirim daha`,
clearAll: 'Tümünü temizle', dismiss: 'Bildirimi kapat', details: 'Ayrıntılar',
copyDetail: 'Ayrıntıyı kopyala', copyDetailFailed: 'Bildirim ayrıntısı kopyalanamadı',
backendOutOfDateTitle: 'Sunucu güncel değil',
backendOutOfDateMessage: 'Ajan sunucunuz bu masaüstü sürümünden eski ve düzgün çalışmayabilir. Sürümleri eşleştirmek için güncelleyin.',
installMethodUnsupportedTitle: 'Desteklenmeyen kurulum yöntemi', updateHermes: 'AITURK IDEyi güncelle',
updateReadyTitle: 'Güncelleme hazır', updateReadyMessage: count => `${count} yeni değişiklik var.`,
updateReadyMessageUnknown: 'Yeni bir güncelleme var.', seeWhatsNew: 'Yenilikleri gör',
mcp: {
needsAuthTitle: 'MCP sunucusunda yeniden giriş gerekiyor', needsAuthMessage: name => `${name} MCP için yeniden kimlik doğrulayın.`,
errorTitle: 'MCP sunucusuna erişilemiyor', errorMessage: name => `${name} MCP sağlık kontrolü başarısız.`,
signIn: 'Giriş yap', view: 'Görüntüle'
},
errors: {
elevenLabsNeedsKey: 'ElevenLabs ses tanıma için ELEVENLABS_API_KEY gerekiyor.',
elevenLabsRejectedKey: 'ElevenLabs API anahtarını reddetti (401).',
diskFull: 'Disk dolu. Yer açıp yeniden deneyin.',
gatewayAuthFailed: 'Ağ geçidi kimlik doğrulaması başarısız. API_SERVER_KEY değerini kontrol edin.',
methodNotAllowed: 'Masaüstü sunucusu isteği reddetti (405 Method Not Allowed). AITURK IDEyi yeniden başlatın.',
microphonePermission: 'Mikrofon izni reddedildi.', openaiRejectedApiKey: 'OpenAI API anahtarını reddetti.',
openaiRejectedApiKeyWithStatus: status => `OpenAI API anahtarını reddetti (${status} invalid_api_key).`,
openaiTtsNeedsKey: 'OpenAI ses üretimi için VOICE_TOOLS_OPENAI_KEY veya OPENAI_API_KEY gerekiyor.',
codeSkewRestartRequired: 'Sunucu güncellemeden sonra eski kodu çalıştırıyor. Yeni kodu yüklemek için yeniden başlatın.'
},
voice: {
configureSpeechToText: 'Sesli modu kullanmak için ses tanımayı ayarlayın.', couldNotStartSession: 'Sesli oturum başlatılamadı',
microphoneAccessDenied: 'Mikrofon erişimi reddedildi.', microphoneConstraintsUnsupported: 'Bu cihaz istenen mikrofon ayarlarını desteklemiyor.',
microphoneFailed: 'Mikrofon hatası', microphoneInUse: 'Mikrofon başka bir uygulama tarafından kullanılıyor.',
microphonePermissionDenied: 'Mikrofon izni reddedildi.', microphoneStartFailed: 'Mikrofon kaydı başlatılamadı.',
microphoneUnsupported: 'Bu çalışma ortamı mikrofon kaydını desteklemiyor.', noMicrophone: 'Mikrofon bulunamadı.',
noSpeechDetected: 'Konuşma algılanmadı', playbackFailed: 'Ses oynatılamadı', recordingFailed: 'Ses kaydedilemedi',
sayStopToEnd: phrase => `Sesli sohbeti bitirmek için “${phrase}” deyin.`, transcriptionFailed: 'Ses metne dönüştürülemedi',
transcriptionUnavailable: 'Ses tanıma henüz kullanılamıyor.', tryRecordingAgain: 'Yeniden kaydetmeyi deneyin.', unavailable: 'Ses kullanılamıyor'
},
native: {
approvalTitle: 'Onay gerekiyor', approveAction: 'Onayla', rejectAction: 'Reddet', inputTitle: 'Yanıt gerekiyor',
inputBody: 'AITURK yanıtınızı bekliyor.', turnDoneTitle: 'AITURK tamamladı', turnDoneBody: '',
turnErrorTitle: 'İşlem başarısız', backgroundDoneTitle: 'Arka plan görevi tamamlandı',
backgroundFailedTitle: 'Arka plan görevi başarısız', creditsTitle: 'Kredi'
}
},
remoteDisplayBanner: { message: reason => `Yazılımla görüntüleme etkin. Uzak ekran algılandı (${reason}). Titremeyi önlemek için GPU hızlandırması kapatıldı.` },
billingBlock: {
titleNous: 'Nous krediniz bitti', titleProvider: provider => `${provider} krediniz bitti`,
fallbackMessage: 'Hesabınızdaki kredi bitti. Devam etmek için kredi ekleyin.', openBilling: 'Faturalandırmayı aç', addCredits: 'Kredi ekle', dismiss: 'Kapat'
},
sendDiagnostics: {
title: 'Nousa tanılama gönder',
privacyNotice: 'Bu işlem hata ayıklama paketini TurkServise değil, Nousun özel depolama alanına gönderir. Sistem bilgileri, sürümler, sağlayıcı, hangi API anahtarlarının ayarlandığı (anahtarların kendileri değil) ve ajan, ağ geçidi ve masaüstü günlükleri (her biri en fazla 512 KB) gönderilir. Günlüklerde sohbet içeriği, araç çıktıları ve dosya yolları bulunabilir. Gizli bilgiler yüklemeden önce ayıklanır. Paketi yalnızca Nous çalışanları ve izin verilen Discord moderatörleri görebilir; paket 14 gün sonra silinir.',
upload: 'Yükle', uploading: 'Yükleniyor…', cancel: 'İptal', close: 'Kapat', copyLink: 'Bağlantıyı kopyala',
uploadIdFallback: id => `Görüntüleme bağlantısı dönmedi. Destek için yükleme kimliği: ${id}`, doneTitle: 'Tanılama gönderildi',
doneDescription: 'Paketiniz özel olarak yüklendi. Günlükleri inceleyebilmeleri için aşağıdaki bağlantıyı destek görüşmenizde paylaşabilirsiniz.',
failedTitle: 'Yükleme başarısız', failedHint: 'Yerel masaüstü günlüğünü açıp hatayı inceleyebilirsiniz.', handoffLead: 'Görüşmeye devam edin:',
links: { github: 'GitHub sorunları', portal: 'Nous Portal desteği', discord: 'Discord' }
},
prompts: {
gatewayDisconnected: 'Ağ geçidi bağlı değil', sudoSendFailed: 'Yönetici parolası gönderilemedi', secretSendFailed: 'Gizli bilgi gönderilemedi',
sudoTitle: 'Yönetici parolası', sudoDesc: 'Ajanın yetkili komutu çalıştırması için sudo parolanız gerekiyor. Yalnızca yerel ajana gönderilir.',
sudoPlaceholder: 'sudo parolası', secretTitle: 'Gizli bilgi gerekiyor', secretDesc: 'Devam etmek için bir kimlik bilgisi gerekiyor.', secretPlaceholder: 'gizli değer'
},
titlebar: {
hideSidebar: 'Kenar çubuğunu gizle', showSidebar: 'Kenar çubuğunu göster', search: 'Ara',
searchTitle: 'Oturum, görünüm ve işlem ara', swapSidebarSides: 'Kenar çubuklarının yerini değiştir',
hideRightSidebar: 'Sağ kenar çubuğunu gizle', showRightSidebar: 'Sağ kenar çubuğunu göster',
unreadSessions: count => `${count} okunmamış oturum`, muteHaptics: 'Dokunsal geri bildirimi kapat',
unmuteHaptics: 'Dokunsal geri bildirimi aç', openSettings: 'Ayarları aç', openStarmap: 'Bellek grafiğini aç',
enterHud: 'HUD görünümüne geç', exitHud: 'HUD görünümünden çık', resetHudLayout: 'HUD düzenini sıfırla',
layoutEditor: 'Düzen editörü', layoutEditorTitle: modifier => `Düzen editörü (${modifier})`
},
findInPage: { next: 'Sonraki eşleşme', previous: 'Önceki eşleşme' },
language: {
label: 'Dil', description: 'Uygulama arayüzünün dilini seçin.', saving: 'Dil kaydediliyor…',
saveError: 'Dil tercihi kaydedilemedi.', switchTo: 'Dili değiştir', searchPlaceholder: 'Dil ara…', noResults: 'Dil bulunamadı.'
},
settings: {
closeSettings: 'Ayarları kapat', exportConfig: 'Ayarları dışa aktar', importConfig: 'Ayarları içe aktar', resetToDefaults: 'Varsayılanlara dön',
resetConfirm: 'Tüm ayarlar varsayılan değerlere dönsün mü?', exportFailed: 'Dışa aktarılamadı', resetFailed: 'Sıfırlanamadı',
nav: { providers: 'Sağlayıcılar', providerAccounts: 'Hesaplar', providerApiKeys: 'API anahtarları', providerCustomEndpoints: 'Özel API bağlantıları',
providerLocalModels: 'Yerel modeller', gateway: 'Ağ geçitleri', apiKeys: 'Araçlar ve anahtarlar', keybinds: 'Klavye kısayolları',
keysTools: 'Araçlar', keysSettings: 'Ayarlar', mcp: 'MCP', archivedChats: 'Arşivlenen sohbetler', about: 'Hakkında', billing: 'Faturalandırma', notifications: 'Bildirimler', plugins: 'Eklentiler' },
sections: { model: 'Model', chat: 'Sohbet', appearance: 'Görünüm', workspace: 'Çalışma alanı', safety: 'Güvenlik', memory: 'Bellek ve bağlam', voice: 'Ses', advanced: 'Gelişmiş' },
searchPlaceholder: { about: 'AITURK IDE hakkında', config: 'Ayarlarda ara…', gateway: 'Ağ geçidi bağlantısı…', keys: 'API anahtarı ara…', mcp: 'MCP sunucusu ara…', sessions: 'Arşivlenen oturumlarda ara…' },
modeOptions: { light: { label: 'Açık', description: 'Açık görünümü kullan' }, dark: { label: 'Koyu', description: 'Koyu görünümü kullan' }, system: { label: 'Sistem', description: 'Sistem ayarını izle' } },
appearance: {
title: 'Görünüm', intro: 'Masaüstü görünümü. Mod parlaklığı; tema ise renkleri ve sohbet görünümünü belirler.',
colorMode: 'Renk modu', colorModeDesc: 'Sabit bir mod seçin veya sistem ayarını izleyin.', toolViewTitle: 'Araç çağrısı görünümü',
toolViewDesc: 'Sade görünüm ham araç verilerini gizler; teknik görünüm tüm giriş ve çıkışları gösterir.',
reasoningCollapsedTitle: 'Düşünme ayrıntılarını başlangıçta daralt', reasoningCollapsedDesc: 'Düşünme içeriği siz açana kadar daraltılmış olarak tutulur.',
uiScaleTitle: 'Arayüz ölçeği', uiScaleDesc: p => `Yazı ve denetimlerin boyutunu ayarlar. Ctrl/Cmd ile +, - ve 0 da kullanılabilir. Mevcut: %${p}.`,
sessionDensityTitle: 'Oturum listesi yoğunluğu', sessionDensityDesc: 'Kenar çubuğundaki oturum başlıklarının altında gösterilecek bilgi miktarını seçin.',
sessionDensityCompact: 'Sıkışık', sessionDensityComfortable: 'Rahat', sessionDensityDetailed: 'Ayrıntılı', tabStripTitle: 'Sekme çubuğu',
tabStripDesc: 'Panellerin üstünde sekmeleri gösterir. Otomatik mod, tek panelli alanlarda sekmeleri gizler.', tabStripAuto: 'Otomatik', tabStripAlways: 'Her zaman', tabStripNever: 'Hiçbir zaman',
terminalFontTitle: 'Terminal yazı tipi', terminalFontDesc: 'Yüklü bir yazı tipi seçin. Nerd Fonts terminal simgelerini destekler. Boş bırakırsanız JetBrains Mono kullanılır.',
terminalFontPlaceholder: 'MesloLGS NF veya CSS yazı tipi listesi', terminalFontPreview: 'Karakter önizlemesi', terminalFontReset: 'Varsayılanı kullan',
translucencyTitle: 'Pencere saydamlığı', translucencyDesc: 'Pencerenin arkasındaki masaüstünü gösterir. Açık ve koyu mod ayrı ayarlanır.',
translucencyGlassDesc: 'Buzlu cam: masaüstü bulanık görünür, yazılar net kalır. Açık ve koyu mod ayrı ayarlanır.',
translucencyModeClear: 'Saydam', translucencyModeGlass: 'Cam', translucencyTintTitle: 'Renk tonu', translucencyFadeTitle: 'Saydamlık',
translucencyFrostTitle: 'Buzlanma', translucencyFrost: { 'under-window': 'Derin', popover: 'Yumuşak', titlebar: 'Parlak', header: 'Işıltılı' },
translucencyScopeTitle: 'Alan', translucencyScope: { window: 'Tüm pencere', sidebar: 'Yalnızca kenar çubuğu' },
backdropTitle: 'Sohbet arka planı', backdropDesc: 'Sohbetin arkasındaki silik arka plan görseli.', introSplashTitle: 'Başlangıç ekranı',
introSplashDesc: 'Boş sohbette görünen marka ve başlangıç istemi.', reactionsTitle: 'Mesaj tepkileri', reactionsDesc: 'Mesajlara emojiyle tepki verin; AITURK de mesajlarınıza tepki verebilir.',
tipsTitle: 'Uygulama ipuçları', tipsDesc: 'Uygulamanın ilgili bölümünü gösteren küçük yardım balonları. Kapatılan ipucu yeniden gösterilmez.',
tipsReset: n => `Kapatılan ${n} ipucunu geri getir`, toursTitle: 'Rehberli turlar', toursDesc: 'Her adımı vurgulayan uygulama tanıtımları.',
composerPopoutTitle: 'Yüzen mesaj alanı', composerPopoutDesc: 'Mesaj alanının sürüklenmesine izin verir. Kapatırsanız altta sabit kalır.',
vibeHeartsTitle: 'Teşekkür kalpleri', vibeHeartsDesc: 'Teşekkür veya kalp gönderdiğinizde yüzen kalpler gösterir.',
embedsTitle: 'Gömülü önizlemeler', embedsDesc: 'YouTube ve X gibi sitelerden önizleme yükler. “Sor” izin ister, “Her zaman” otomatik yükler, “Kapalı” yalnızca bağlantıyı gösterir.',
embedsAsk: 'Sor', embedsAlways: 'Her zaman', embedsOff: 'Kapalı', embedsReset: n => `İzin verilen ${n} hizmeti sıfırla`,
resumeLastSessionTitle: 'Açılışta son sohbeti aç', resumeLastSessionDesc: 'Son sohbetinizle devam eder. Kapatırsanız her açılışta yeni sohbet başlar.',
product: 'Sade', productDesc: 'Kısa ve okunabilir araç etkinliği özetleri.', technical: 'Teknik', technicalDesc: 'Ham araç girişlerini, sonuçlarını ve teknik ayrıntıları gösterir.',
themeTitle: 'Tema', themeDesc: 'Masaüstü renk paleti. Seçilen renk modu bu temaya uygulanır.', themeProfileNote: p => `${p} profiline kaydedilir. Her profilin teması ayrıdır.`,
installTitle: 'VS Code teması yükle', installDesc: 'Marketplace eklenti kimliğini yapıştırın (örn. dracula-theme.theme-dracula). Renkler masaüstü temasına dönüştürülür.',
installPlaceholder: 'yayinci.eklenti', installed: name => `${name}” yüklendi.`, removeTheme: 'Temayı kaldır', importedBadge: 'İçe aktarıldı'
},
about: {
heading: 'AITURK IDE', version: v => `Sürüm ${v}`, versionUnavailable: 'Sürüm bilgisi yok', bundleOutOfSync: 'Uygulama derlemesi eski',
bundleOutOfSyncDesc: 'Ajan çalışma ortamı güncellendi ancak masaüstü uygulaması eski kaldı. Resmî indirme sayfasından güncel uygulama paketini kurun.',
bundleOutOfSyncAction: 'Kurulum paketini indir', bundleSwapPending: 'Güncellemeyi tamamlamak için yeniden başlatın',
bundleSwapPendingDesc: 'Güncel uygulama kuruldu. Sohbetleriniz ve ayarlarınız korunarak yeniden başlatılabilir.', bundleSwapPendingAction: 'AITURK IDEyi yeniden başlat',
updates: 'Güncellemeler', checkNow: 'Şimdi kontrol et', checking: 'Kontrol ediliyor…', seeWhatsNew: 'Yenilikleri gör', updateNow: 'Şimdi güncelle',
releaseNotes: 'Sürüm notları', onLatest: 'En güncel sürümü kullanıyorsunuz.', installing: 'Bir güncelleme kuruluyor.',
cantUpdate: 'Bu dağıtım resmî AITURK IDE kurulum paketiyle güncellenir.', cantReach: 'Güncelleme sunucusuna erişilemedi.', tapCheck: 'Güncellemeleri aramak için “Şimdi kontrol et” seçeneğine tıklayın.',
updateReady: n => `Yeni güncelleme hazır (${n} değişiklik).`, updateReadyUnknown: 'Yeni güncelleme hazır.', lastChecked: age => `Son kontrol: ${age}`, justNowSuffix: ' · az önce',
automaticUpdates: 'Otomatik güncelleme kontrolü', automaticUpdatesDesc: 'Güncellemeler arka planda kontrol edilir. AITURK masaüstü paketi resmî indirme sayfasından güncellenir.',
branchCommit: (branch, commit) => `Dal ${branch} · Commit ${commit}`, never: 'hiç', justNow: 'az önce', minAgo: n => `${n} dakika önce`, hoursAgo: n => `${n} saat önce`, daysAgo: n => `${n} gün önce`
},
config: {
none: 'Yok', noneParen: '(yok)', builtinOnly: 'Yalnızca yerleşik', notSet: 'Ayarlanmadı', commaSeparated: 'virgülle ayrılmış değerler',
searchPlaceholder: 'Ara…', noResults: 'Sonuç bulunamadı', systemDefault: 'Sistem varsayılanı', loading: 'Yapılandırma yükleniyor…',
emptyTitle: 'Ayarlanacak seçenek yok', emptyDesc: 'Bu bölümde değiştirilebilen ayar yok.', failedLoad: 'Ayarlar yüklenemedi', autosaveFailed: 'Otomatik kaydetme başarısız',
imported: 'Ayarlar içe aktarıldı', invalidJson: 'Geçersiz JSON yapılandırması',
toolsetsWipeConfirm: 'Etkin tüm araç setleri kaldırılsın mı? Bellek, terminal, web araması ve çoğu araç yeniden etkinleştirilene kadar kapanır.',
keepAwakeTitle: 'Bilgisayarı uyanık tut', keepAwakeDesc: 'Uzun süren işlerin devam etmesi için uyku modunu engeller. Ekran yine kararabilir.',
disableF12Title: 'F12 geliştirici araçlarını kapat', disableF12Desc: 'F12 ile geliştirici araçlarının açılmasını engeller. Ctrl+Shift+I (Macte Cmd+Opt+I) çalışmaya devam eder.',
attachmentSizeTitle: 'En büyük önizleme / görsel boyutu', attachmentSizeDesc: 'Önizleme ve görsel ekleri için yerel dosya sınırı (MB). Varsayılan 16 MB. Çok yüksek değerler uygulamanın belleğini tüketebilir.',
attachmentSizeUnit: 'MB', attachmentSizeLabel: 'Megabayt cinsinden önizleme ve görsel boyutu sınırı'
},
credentials: { pasteKey: 'Anahtarı yapıştır', pasteLabelKey: label => `${label} anahtarını yapıştır`, optional: 'İsteğe bağlı', enterValueFirst: 'Önce bir değer girin.', couldNotSave: 'Kimlik bilgisi kaydedilemedi.', remove: 'Kaldır', getKey: 'Anahtar al', saving: 'Kaydediliyor' },
envActions: { actions: 'İşlemler', manageInKeys: 'API anahtarlarında yönet', docs: 'Belgeler', hideValue: 'Değeri gizle', revealValue: 'Değeri göster', replace: 'Değiştir', set: 'Ayarla', clear: 'Temizle' },
providers: {
connectAccount: 'Hesap bağla', haveApiKey: 'API anahtarınız mı var?', intro: 'Sağlayıcınızın aboneliğiyle tarayıcı üzerinden giriş yapın veya API anahtarı kullanın.',
connected: 'Bağlandı', collapse: 'Daralt', connectAnother: 'Başka sağlayıcı bağla', otherProviders: 'Diğer sağlayıcılar', disconnect: 'Bağlantıyı kes',
disconnectInTerminal: 'Bağlantıyı kes (kaldırma komutunu terminalde çalıştırır)', removeConfirm: p => `${p} kaldırılsın mı?`,
removeExternalGeneric: p => `${p} kendi CLI aracıyla yönetilir; hesabı oradan kaldırın.`, removeKeyManaged: p => `${p} API anahtarıyla yapılandırılmış. API anahtarları bölümünden kaldırın.`,
removeTerminalConfirm: (p, c) => `${p} bağlantısı kesilsin mi? Kimlik bilgisini kaldırmak için terminalde “${c}” çalıştırılır.`,
removeTerminalRunning: p => `${p} bağlantısı terminalde kesiliyor…`, removedTitle: 'Hesap kaldırıldı', removedMessage: p => `${p} kaldırıldı.`,
failedRemove: p => `${p} kaldırılamadı`, noProviderKeys: 'Sağlayıcı API anahtarı yok.', searchKeys: 'Sağlayıcı ara…', noKeysMatch: 'Eşleşen sağlayıcı yok.',
localEndpoint: { title: 'Yerel / özel API bağlantısı', description: 'TurkServis veya başka bir OpenAI uyumlu API adresine bağlanın.' }, loading: 'Sağlayıcılar yükleniyor…'
},
sessions: {
loading: 'Arşivlenen oturumlar yükleniyor…', archivedTitle: 'Arşivlenen oturumlar', archivedIntro: 'Arşivlenen sohbetler kenar çubuğundan gizlenir; mesajları korunur. Arşivlemek için Ctrl/⌘ ile sohbete tıklayın.',
emptyArchivedTitle: 'Arşiv boş', emptyArchivedDesc: 'Arşivlediğiniz sohbetler burada görünür.', unarchive: 'Arşivden çıkar', deletePermanently: 'Kalıcı olarak sil',
messages: n => `${n} mesaj`, restored: 'Geri yüklendi', deleteConfirm: title => `${title}” kalıcı olarak silinsin mi? Bu işlem geri alınamaz.`,
autoArchiveTitle: 'Eski sohbetleri otomatik arşivle', autoArchiveDesc: 'Bir süredir kullanılmayan sohbetleri arşivler. Sabitlenen sohbetler arşivlenmez; hiçbir mesaj silinmez.',
autoArchiveDaysLabel: 'Arşivleme süresi', autoArchiveDaysUnit: 'gün kullanılmadığında', autoArchiveFailed: 'Otomatik arşivleme güncellenemedi',
defaultDirTitle: 'Varsayılan proje klasörü', defaultDirDesc: 'Başka klasör seçilmezse yeni oturumlar burada başlar. Boş bırakırsanız kullanıcı klasörünüz kullanılır.',
defaultDirUpdated: 'Varsayılan klasör güncellendi. Uygulanması için yeni sohbet açın (Ctrl/⌘+N).', defaultsTo: p => `Varsayılan: ${p}.`,
change: 'Değiştir', choose: 'Seç', clear: 'Temizle', notSet: 'Ayarlanmadı', failedLoad: 'Arşivlenen oturumlar yüklenemedi',
unarchiveFailed: 'Arşivden çıkarılamadı', deleteFailed: 'Silinemedi', updateDirFailed: 'Varsayılan klasör güncellenemedi', clearDirFailed: 'Varsayılan klasör temizlenemedi'
},
notifications: {
title: 'Bildirimler', intro: 'Bu cihazın işletim sistemi bildirimleri.', enableAll: 'Bildirimleri etkinleştir', enableAllDesc: 'Kapatırsanız aşağıdaki tüm bildirimler susturulur.',
focusedHint: 'Tamamlanma bildirimleri uygulama arka plandayken gösterilir.',
kinds: {
approval: { label: 'Onay gerekiyor', description: 'Bir komut onayınızı veya reddinizi bekliyor.' },
input: { label: 'Yanıt gerekiyor', description: 'AITURK bir soru, parola veya gizli bilgi için yanıt bekliyor.' },
turnDone: { label: 'Yanıt hazır', description: 'Uygulama arka plandayken bir işlem tamamlandı.' },
turnError: { label: 'İşlem başarısız', description: 'Arka plandaki işlem hataları.' },
backgroundDone: { label: 'Arka plan görevi tamamlandı', description: 'Arka plandaki terminal komutu tamamlandı.' },
credits: { label: 'Kredi bildirimleri', description: 'Kredi erişimi durduruldu veya yeniden açıldı.' },
plugin: { label: 'Eklenti bildirimleri', description: 'Uygulama arka plandayken masaüstü eklentisi bildirim gönderdi.' }
},
test: 'Test bildirimi gönder', testTitle: 'AITURK IDE', testBody: 'Bildirimler çalışıyor.',
testSent: 'Test gönderildi. Görünmüyorsa sistem bildirim izinlerini ve Rahatsız Etmeyin modunu kontrol edin.',
testUnsupported: 'Bu sistem yerel bildirimleri desteklemiyor.', completionSoundTitle: 'Tamamlanma sesi',
completionSoundDesc: 'Ajan işi bitirdiğinde çalar. Bir ses seçip dinleyin.', completionSoundPreview: 'Dinle'
}
},
errors: { genericFailure: 'Bir hata oluştu', boundaryTitle: 'Arayüzde bir hata oluştu', boundaryDesc: 'Görünüm beklenmeyen bir hatayla karşılaştı. Sohbetleriniz ve ayarlarınız korunuyor.', reloadWindow: 'Pencereyi yenile', openLogs: 'Günlükleri aç' },
ui: {
search: { clear: 'Aramayı temizle' }, pagination: { label: 'sayfalama', previous: 'Önceki', previousAria: 'Önceki sayfaya git', next: 'Sonraki', nextAria: 'Sonraki sayfaya git' },
sidebar: { title: 'Kenar çubuğu', description: 'Mobil kenar çubuğunu gösterir.', toggle: open => `Kenar çubuğunu ${open ? 'göster' : 'gizle'}` }
},
install: {
stageStates: { pending: 'Bekliyor', running: 'Kuruluyor', succeeded: 'Tamamlandı', skipped: 'Atlandı', failed: 'Başarısız' },
oneTimeTitle: 'AITURK IDE için ilk kurulum gerekiyor',
unsupportedDesc: platform => `${platform} üzerinde otomatik ilk kurulum henüz desteklenmiyor. Terminalde aşağıdaki komutu çalıştırıp uygulamayı yeniden açın. Sonraki açılışlarda bu adım atlanır.`,
installCommand: 'Kurulum komutu', copyCommand: 'Komutu kopyala', viewDocs: 'Kurulum belgeleri', installTo: 'Kurulum konumu',
retryAfterRun: 'Komutu çalıştırdım — yeniden dene', setupChoiceTitle: 'AITURK IDE kurulumu',
setupChoiceDesc: 'Çalışan bir Hermes uyumlu ağ geçidine bağlanın veya ajanı bu bilgisayara kurun.',
connectExistingTitle: 'Mevcut ağ geçidine bağlan', connectExistingShort: 'Mevcut bağlantı',
connectExistingDesc: 'Oturum anahtarı veya tarayıcı girişiyle uzak sunucu kullanın. Yerel kurulum başlatılmaz.',
installLocalTitle: 'Bu bilgisayara kur', installLocalDesc: 'Ajanı indirin, Python ortamını oluşturun ve sunucuyu bu bilgisayarda çalıştırın.',
localStartUnavailable: 'Yerel kurulum başlatılamadı. AITURK IDEyi yeniden başlatıp deneyin.',
remoteSetupTitle: 'Mevcut ağ geçidine bağlan', remoteSetupDesc: 'Ağ geçidi adresini girin. AITURK IDE gereken giriş yöntemini algılar.',
remoteUrlTitle: 'Ağ geçidi adresi', remoteUrlDesc: 'Hermes uyumlu ağ geçidinin temel adresini girin. Uzak sunucularda https:// kullanın.',
remoteUrlPlaceholder: 'https://gateway.example.com/hermes', probing: 'Kimlik doğrulama yöntemi algılanıyor…',
probeError: 'Bu ağ geçidine erişilemedi.', identityProvider: 'kimlik sağlayıcınız', authTitle: 'Kimlik doğrulama',
authNeedsOauth: provider => `Bağlantıyı sınamadan önce ${provider} ile giriş yapın.`, authSignedIn: 'Tarayıcı girişi tamamlandı.',
connected: 'Bağlandı', signIn: 'Giriş yap', signInWith: provider => `${provider} ile giriş yap`, enterUrlFirst: 'Önce ağ geçidi adresini girin.',
signInIncomplete: 'Kimlik doğrulama bitmeden giriş penceresi kapandı.', tokenTitle: 'Oturum anahtarı',
tokenDesc: 'Uzak ağ geçidinin .env dosyasındaki oturum anahtarını yapıştırın.', pasteSessionToken: 'Oturum anahtarını yapıştır',
incompleteSignInTest: 'OAuth korumalı ağ geçidini sınamadan önce giriş yapın.', incompleteTokenTest: 'Bağlantıyı sınamadan önce oturum anahtarını girin.',
testConnection: 'Bağlantıyı sına', testSucceeded: (url, version) => `${url} bağlantısı kuruldu${version ? ` (${version})` : ''}.`,
applyRemote: 'Uygula ve yeniden bağlan', backToSetup: 'Geri', failedTitle: 'Kurulum başarısız', settingUpTitle: 'AITURK ajanı kuruluyor',
finishingTitle: 'Tamamlanıyor',
failedDesc: 'Kurulum adımlarından biri başarısız oldu. Aynı kurulumdaki başka bir ajan işlemi buna neden olabilir. Ayrıntıları veya masaüstü günlüğünü inceleyip yeniden deneyin.',
activeDesc: 'Bu kurulum bir kez yapılır. Bağımlılıklar indiriliyor ve çalışma ortamı hazırlanıyor. Sonraki açılışlarda bu adım atlanır.',
progress: (completed, total) => `${total} adımın ${completed} tanesi tamamlandı`, currentStage: stage => ` — şimdi: ${stage}`,
fetchingManifest: 'Kurulum bildirimi alınıyor…', error: 'Hata', hideOutput: 'Kurulum çıktısını gizle', showOutput: 'Kurulum çıktısını göster',
lines: count => `${count} satır`, noOutput: 'Henüz çıktı yok.', cancelling: 'İptal ediliyor…', cancelInstall: 'Kurulumu iptal et',
transcriptSaved: 'Tam günlük şuraya kaydedildi', copiedOutput: 'Kopyalandı!', copyOutput: 'Çıktıyı kopyala', reloadRetry: 'Yenile ve yeniden dene'
},
onboarding: {
headerTitle: 'AITURK IDEye hoş geldiniz', headerDesc: 'Sohbete başlamak için bir model sağlayıcı bağlayın.',
preparingInstall: 'AITURK kurulumu tamamlıyor. İlk açılışta genellikle bir dakikadan kısa sürer.', starting: 'AITURK başlatılıyor…',
lookingUpProviders: 'Sağlayıcılar aranıyor…', collapse: 'Daralt', otherProviders: 'Diğer sağlayıcılar', haveApiKey: 'API anahtarım var',
chooseLater: 'Sağlayıcıyı daha sonra seçeceğim', recommended: 'Önerilen', connected: 'Bağlandı',
featuredPitch: 'Nous aboneliğiyle birden fazla yapay zekâ modeline erişim', fireworksPitch: 'Fireworks tarafından sunulan modellere doğrudan API erişimi',
localModelsTitle: 'Modelleri yerelde çalıştır', localModelsPitch: 'Hesap gerekmez. Model indirip bu bilgisayarda çalıştırın.',
openRouterPitch: 'Tek anahtarla yüzlerce model',
apiKeyOptions: {
fireworks: { short: 'doğrudan model APIsi', description: 'Fireworks AI modellerine doğrudan erişim.' },
openrouter: { short: 'tek anahtar, çok model', description: 'Tek API anahtarıyla yüzlerce modele erişim.' },
openai: { short: 'GPT modelleri', description: 'OpenAI modellerine doğrudan erişim.' },
gemini: { short: 'Gemini modelleri', description: 'Google Gemini modellerine doğrudan erişim.' },
xai: { short: 'Grok modelleri', description: 'xAI Grok modellerine doğrudan erişim.' },
local: { short: 'kendi sunucunuz', description: 'OpenAI uyumlu yerel veya uzak bir API adresine bağlanın (vLLM, llama.cpp, Ollama vb.).' }
},
backToSignIn: 'Girişe dön', getKey: 'Anahtar al', replaceCurrent: 'Mevcut değeri değiştir', pasteApiKey: 'API anahtarını yapıştır',
localApiKeyPlaceholder: 'API anahtarı (sunucunuz gerektiriyorsa)', couldNotSave: 'Kimlik bilgisi kaydedilemedi.', connecting: 'Bağlanıyor', update: 'Güncelle',
flowSubtitles: { pkce: 'Giriş için tarayıcı açılır; ardından buradan devam edilir', device_code: 'Tarayıcıda doğrulama sayfası açılır; bağlantı otomatik kurulur', external: 'Terminalde bir kez giriş yapıp sohbete dönün' },
startingSignIn: p => `${p} girişi başlatılıyor…`, verifyingCode: p => `${p} doğrulama kodunuz kontrol ediliyor…`,
connectedProvider: p => `${p} bağlandı`, connectedPicking: p => `${p} bağlandı. Varsayılan model seçiliyor…`,
signInFailed: 'Giriş başarısız. Yeniden deneyin.',
signInExpired: 'Giriş için bekleme süresi doldu. Açılan tarayıcı sayfasında girişi tamamlayıp yeniden deneyin. Sorun sürerse API anahtarı veya CLI girişini kullanın.',
pickDifferentProvider: 'Başka sağlayıcı seç', signInWith: p => `${p} ile giriş yap`, openedBrowser: p => `${p} tarayıcınızda açıldı.`,
authorizeThere: 'Açılan sayfada erişim izni verin.', copyAuthCode: 'Yetkilendirme kodunu kopyalayıp aşağıya yapıştırın.',
pasteAuthCode: 'Yetkilendirme kodunu yapıştır', reopenAuthPage: 'Yetkilendirme sayfasını yeniden aç',
autoBrowser: p => `${p} tarayıcınızda açıldı. Açılan sayfada erişim izni verin; bağlantı otomatik kurulur.`,
reopenSignInPage: 'Giriş sayfasını yeniden aç', waitingAuthorize: 'Yetkilendirmeniz bekleniyor…',
externalPending: p => `${p} kendi CLI aracıyla giriş yapar. Aşağıdaki komutu terminalde çalıştırıp “Giriş yaptım” seçeneğine tıklayın:`,
signedIn: 'Giriş yaptım', deviceCodeOpened: p => `${p} tarayıcınızda açıldı. Şu kodu girin:`, reopenVerification: 'Doğrulama sayfasını yeniden aç',
copy: 'Kopyala', defaultModel: 'Varsayılan model', freeTier: 'Ücretsiz plan', pro: 'Pro', free: 'Ücretsiz',
price: (input, output) => `Milyon token başına ${input} giriş / ${output} çıkış`, change: 'Değiştir', startChatting: 'Başla', docs: p => `${p} belgeleri`
},
modelPicker: {
title: 'Model değiştir', current: 'mevcut:', unknown: '(bilinmiyor)', search: 'Sağlayıcı ve model ara…',
noModels: 'Model bulunamadı.', addProvider: 'Sağlayıcı ekle', loadFailed: 'Modeller yüklenemedi',
loadingIntoMemory: 'Belleğe yükleniyor', downloading: 'İndiriliyor', localDownloadsHeading: 'Yerel',
noAuthenticatedProviders: 'Giriş yapılmış sağlayıcı yok.', pro: 'Pro', proNeedsSubscription: 'Pro modeller için ücretli Nous aboneliği gerekir.',
free: 'Ücretsiz', freeTier: 'Ücretsiz plan', priceTitle: 'Milyon token başına giriş / çıkış fiyatı', wasPrice: 'önceki fiyat'
},
modelVisibility: { title: 'Modeller', search: 'Model ara', noAuthenticatedProviders: 'Giriş yapılmış sağlayıcı yok.', addProvider: 'Sağlayıcı ekle…' },
sidebar: {
nav: { 'new-session': 'Yeni oturum', skills: 'Yetenekler', messaging: 'Mesajlaşma', artifacts: 'Çıktılar', cron: 'Zamanlanmış işler' },
searchAria: 'Oturum ara', searchPlaceholder: 'Oturum ara…', clearSearch: 'Aramayı temizle',
noMatch: q => `${q}” ile eşleşen oturum yok.`, results: 'Sonuçlar', pinned: 'Sabitlenenler', sessions: 'Oturumlar', cronJobs: 'Zamanlanmış işler',
groupAriaGrouped: 'Oturumları tek liste olarak göster', groupAriaUngrouped: 'Oturumları çalışma alanına göre grupla',
showProjects: 'Projeleri göster', showSessions: 'Oturumları göster', groupTitleGrouped: 'Gruplamayı kaldır', groupTitleUngrouped: 'Çalışma alanına göre grupla',
allPinned: 'Buradaki tüm sohbetler sabitlenmiş. Son oturumlarda görmek için bir sohbetin sabitlemesini kaldırın.',
shiftClickHint: 'Sabitlemek için Shift tuşuyla tıklayın', noWorkspace: 'Çalışma alanı yok', projectEmpty: 'Henüz oturum yok',
noSessions: 'Henüz oturum yok', noFilterMatches: 'Bu filtrelerle eşleşen oturum yok',
projects: {
sectionLabel: 'Projeler', home: 'Ana sayfa', newButton: 'Yeni proje', createTitle: 'Yeni proje', createDesc: 'Çalışma alanına ad verin ve klasör ekleyin.',
renameTitle: 'Projeyi yeniden adlandır', addFolderTitle: 'Klasör ekle', namePlaceholder: 'Örn. Web sitem', foldersLabel: 'Klasörler',
ideaLabel: 'Fikir', ideaPlaceholder: 'Bu proje ne hakkında? (IDEA.md dosyasına kaydedilir)', ideaGenerate: 'Fikir oluştur',
ideaGenerating: 'Oluşturuluyor…', ideaShuffle: 'Şablonları karıştır', noFolders: 'Henüz klasör eklenmedi.', addFolder: 'Klasör ekle',
primaryBadge: 'birincil', removeFolder: 'Kaldır', create: 'Oluştur', menu: 'İşlemler', menuRename: 'Yeniden adlandır…',
menuAppearance: 'Görünüm', noColor: 'Renk yok', menuAddFolder: 'Klasör ekle', menuSetActive: 'Etkinleştir', menuDelete: 'Sil',
moveToProject: 'Projeye taşı', movedTo: name => `${name} projesine taşındı`, moveFailed: 'Oturum taşınamadı',
moveNoFolder: 'Bu projede taşınacak klasör yok', moveNoProjects: 'Başka proje yok', reveal: 'Klasörde göster', copyPath: 'Yolu kopyala',
removeFromSidebar: 'Kenar çubuğundan gizle', createFailed: 'Proje oluşturulamadı',
staleBackend: 'Proje oluşturmak için ajan sunucusunu güncelleyin. Sunucunuz bu masaüstü uygulamasından eski (Ayarlar → Güncellemeler → Sunucu).',
deleteConfirm: 'Kayıtlı proje uygulamadan kaldırılır. Dosyalar, Git depoları ve çalışma ağaçları korunur.',
startWork: 'Yeni çalışma ağacı', newWorktreeTitle: 'Yeni çalışma ağacı', newWorktreeDesc: 'Bu çalışma ağacının dalına ad verin.',
branchPlaceholder: 'Örn. yeni-ozellik', branchOff: () => ({ before: '', after: ' dalından oluştur' }),
baseBranchPlaceholder: 'Dal ara…', baseBranchNone: 'Dal bulunamadı', startWorkFailed: 'Çalışma ağacı oluşturulamadı',
worktreeStaleBackend: 'Uzak bağlantıda çalışma ağacı oluşturmak için ajan sunucusunu güncelleyin. Mevcut sürüm gerekli Git APIsini desteklemiyor.',
worktreeProjectLabel: 'Proje', worktreeProjectPlaceholder: 'Proje ara…', worktreeProjectNone: 'Klasörü olan proje yok',
convertBranch: 'Dal dönüştür…', convertBranchTitle: 'Dal dönüştür', convertBranchDesc: 'Kullanımdaki bir dalı açın veya boşta olan dal için çalışma ağacı oluşturun.',
convertBranchPlaceholder: 'Dal ara…', convertBranchInstead: 'Mevcut dalı dönüştür', branchOpenExisting: 'aç', branchSwitchHome: 'ana dizine geç',
branchCreateWorktree: 'yeni çalışma ağacı', branchTrackRemote: 'uzak dalı izle', branchesLoading: 'Dallar yükleniyor…', noBranches: 'Dal bulunamadı',
removeWorktree: 'Çalışma ağacını kaldır', removeWorktreeFailed: 'Çalışma ağacı kaldırılamadı. Kaydedilmemiş değişiklikleri kontrol edin.',
removeWorktreeConfirm: 'Gitten kaldırmak çalışma ağacı klasörünü siler, dalı korur. Yalnızca kenar çubuğundan gizlemek ise klasörü diskte tutar.',
removeWorktreeDirty: 'Bu çalışma ağacında commit edilmemiş değişiklikler var. Zorla kaldırırsanız değişiklikler silinir. Yalnızca gizleyerek diskte tutabilirsiniz.',
forceRemove: 'Zorla kaldır', enter: name => `${name}`, reorder: name => `${name} sırasını değiştir`,
toggle: (name, open) => `${name} oturumlarını ${open ? 'göster' : 'gizle'}`, back: 'Tüm projeler'
},
newSessionIn: name => `${name} içinde yeni oturum`, showMoreIn: (count, name) => `${name} içinde ${count} oturum daha göster`,
loading: 'Yükleniyor…', loadMore: 'Daha fazla yükle', loadCount: n => `${n} tane daha yükle`, messageCount: n => `${n} mesaj`, toolCallCount: n => `${n} araç çağrısı`,
row: {
pin: 'Sabitle', unpin: 'Sabitlemeyi kaldır', markUnread: 'Okunmadı olarak işaretle', markRead: 'Okundu olarak işaretle',
unreadFailed: 'Okunma durumu güncellenemedi', copyId: 'Kimliği kopyala', export: 'Dışa aktar', branchFrom: 'Dallandır', rename: 'Yeniden adlandır…',
archive: 'Arşivle', newWindow: 'Yeni pencere', openInTerminal: 'Terminalde aç', hideTabBar: 'Sekme çubuğunu gizle', openInNewTab: 'Yeni sekmede aç',
openInSplit: 'Bölünmüş görünümde aç', copyIdFailed: 'Oturum kimliği kopyalanamadı', sessionActions: 'Oturum işlemleri',
sessionRunning: 'Oturum çalışıyor', needsInput: 'Yanıtınız gerekiyor', waitingForAnswer: 'Yanıtınız bekleniyor', finishedUnread: 'Tamamlandı — okunmadı',
backgroundRunning: 'Arka plan görevi çalışıyor', draftSession: 'Taslak — henüz gönderilmedi', handoffOrigin: p => `${p} üzerinden aktarıldı`,
ownedByProfile: p => `Profil: ${p}`, renamed: 'Yeniden adlandırıldı', renameFailed: 'Yeniden adlandırılamadı', renameTitle: 'Oturumu yeniden adlandır',
renameDesc: 'Adı temizlemek için boş bırakın.', untitledPlaceholder: 'Adsız oturum', deleteTitle: 'Oturum silinsin mi?',
deleteDesc: title => `${title}” kalıcı olarak silinecek. Bu işlem geri alınamaz.`, deleting: 'Siliniyor…', deleted: 'Oturum silindi',
untitledChat: id => `Sohbet ${id}`, messageCount: n => `${n} mesaj`, todoProgress: 'Tamamlanan görevler', ageNow: 'şimdi', ageDay: 'g', ageHour: 'sa', ageMin: 'dk'
},
dateDivider: { today: 'Bugün', yesterday: 'Dün', thisWeek: 'Bu hafta', lastWeek: 'Geçen hafta', thisMonth: 'Bu ay' },
statusDivider: { working: 'Çalışıyor', done: 'Tamamlandı' }, markAllRead: 'Tümünü okundu olarak işaretle'
},
composer: {
message: 'Mesaj', wakingProfile: p => `${p} başlatılıyor…`, placeholderStarting: 'AITURK başlatılıyor…',
placeholderReconnecting: 'AITURKe yeniden bağlanılıyor…', placeholderFollowUp: 'Devam mesajı gönder',
newSessionPlaceholders: ['Ne geliştirelim?', 'AITURKe bir görev verin', 'Aklınızda ne var?', 'Neye ihtiyacınız olduğunu anlatın', 'Nereden başlayalım?', 'İstediğinizi sorun', 'Bir hedefle başlayın'],
followUpPlaceholders: ['Devam mesajı gönderin', 'Daha fazla bilgi ekleyin', 'İsteği netleştirin', 'Sırada ne var?', 'Devam edelim', 'Biraz daha geliştirelim', 'Düzenleyin veya devam edin'],
startVoice: 'Sesli sohbet başlat', openDirective: 'Aç', queueMessage: 'Mesajı sıraya ekle', steer: 'Devam eden işi yönlendir', stop: 'Durdur', send: 'Gönder',
speaking: 'Konuşuyor', transcribing: 'Metne dönüştürülüyor', thinking: 'Düşünüyor', muted: 'Sessiz', listening: 'Dinliyor',
muteMic: 'Mikrofonu kapat', unmuteMic: 'Mikrofonu aç', stopListening: 'Dinlemeyi durdur ve gönder', stopShort: 'Durdur',
endConversation: 'Sesli sohbeti bitir', endShort: 'Bitir', stopDictation: 'Dikteyi durdur', transcribingDictation: 'Dikte metne dönüştürülüyor',
voiceControls: 'Ses', voiceDictation: 'Sesli dikte', speakReplies: 'Yanıtları sesli oku', stopSpeakingReplies: 'Sesli okumayı durdur',
wakeWordListening: p => `Uyandırma sözü: “${p}” — dinliyor`, wakeWordOff: p => `Uyandırma sözü: “${p}” — kapalı`,
wakeWordPausedVoice: p => `Uyandırma sözü: “${p}” — sesli sohbet sırasında duraklatıldı`,
lookupLoading: 'Aranıyor…', lookupNoMatches: 'Eşleşme yok.', lookupTry: 'Deneyin', lookupOr: 'veya', commonCommands: 'Sık kullanılan komutlar',
hotkeys: 'Kısayollar', helpFooter: 'tam paneli açar · Backspace kapatır',
commandDescs: { '/help': 'tüm komutlar ve kısayollar', '/clear': 'yeni oturum başlat', '/resume': 'önceki oturuma devam et', '/details': 'günlük ayrıntı düzeyini ayarla', '/copy': 'seçimi veya son asistan mesajını kopyala', '/quit': 'uygulamadan çık' },
hotkeyDescs: { 'composer.mention': 'dosya, klasör, adres veya Git referansı ekle', 'composer.slash': 'komut paleti', 'composer.help': 'hızlı yardım (silerek kapatın)', 'composer.sendNewline': 'gönder · yeni satır için Shift+Enter', 'composer.sendQueued': 'sıradaki mesajı gönder', 'keybinds.openPanel': 'tüm klavye kısayolları', 'composer.cancel': 'açılır pencereyi kapat · işi iptal et', 'composer.history': 'seçenekler / geçmiş arasında gezin' },
attachUrlTitle: 'Adres ekle', attachUrlDesc: 'AITURK sayfayı alıp bu isteğin bağlamına ekler.', urlPlaceholder: 'https://example.com/yazi',
urlHintPre: 'Tam adresi girin, örneğin ', attach: 'Ekle', queued: n => `${n} sırada`, queuedPaused: n => `${n} sırada — duraklatıldı`,
attachmentOnly: 'Yalnızca ek içeren mesaj', emptyTurn: 'Boş mesaj', attachments: n => `${n} ek`, editingInComposer: 'Mesaj alanında düzenleniyor',
editingQueuedInComposer: 'Sıradaki mesaj düzenleniyor', queueEdit: 'Düzenle', queueSendNext: 'Sonraki', queueSteer: 'Yönlendir — devam eden işi şimdi değiştir',
queueSend: 'Gönder', queueDelete: 'Sil', queueResume: 'Devam et', queueResumeTip: 'Durduruldu — sıradaki mesajları göndermeye devam et',
queueStuckTitle: 'Sıradaki mesaj gönderilmedi', queueStuckBody: 'Mesaj gönderimi başarısız oldu. Mesaj hâlâ sırada; yeniden göndermeyi deneyin.',
previewUnavailable: 'Önizleme kullanılamıyor', previewLabel: n => `${n} önizle`, couldNotPreview: n => `${n} önizlenemedi`, removeAttachment: n => `${n} kaldır`,
dictating: 'Dikte ediliyor', preparingAudio: 'Ses hazırlanıyor', speakingResponse: 'Yanıt seslendiriliyor', readingAloud: 'Sesli okunuyor',
themeSuggestions: 'Masaüstü teması önerileri', noMatchingThemes: 'Eşleşen tema yok.', themeTryPre: '', themeTryPost: ' deneyin.',
attachLabel: 'Ekle', files: 'Dosyalar…', folder: 'Klasör…', images: 'Görseller…', pasteImage: 'Görsel yapıştır', url: 'Adres…', promptSnippets: 'İstem şablonları…',
tipPre: 'İpucu: Dosya referansı eklemek için ', tipPost: ' yazın.', snippetsTitle: 'İstem şablonları', snippetsDesc: 'Mesajınıza eklenecek başlangıç istemini seçin.',
dropFiles: 'Eklemek için dosyaları bırakın', dropSession: 'Bu sohbeti bağlamak için bırakın',
mcpSuggestions: { label: s => `${s} ekle`, tip: k => `${k}” ifadesini kullandığınız için önerildi; bağlanmak için tıklayın`, connecting: s => `${s} bağlanıyor…`, cancelTip: 'İptal için tıklayın', added: s => `${s} eklendi`, addedTip: 'Bağlandı — araçları bu sohbette hazır', connectFailed: s => `${s} bağlanamadı` },
skillSuggestions: { label: s => `Yeteneği kullan: ${s}`, tip: s => `${s}” ifadesini kullandınız; bu yetenekle başlamak için tıklayın`, done: s => `/${s} eklendi`, doneTip: 'Mesaj gönderildiğinde yetenek yüklenir' },
githubSuggestions: { label: 'GitHub bağla', tip: 'GitHub bağlantısı gh CLI yetenekleriyle çalışır; hesabınızı bağlamak için tıklayın', done: '/github-auth eklendi', doneTip: 'Mesajı gönderin; ajan GitHub girişinde size yardımcı olur' },
repairSuggestions: { label: s => `${s} yeniden bağla`, tip: s => `${s} çağrısında bağlantı hatası oluştu`, working: s => `${s} yeniden bağlanıyor…`, workingTip: 'İptal için tıklayın', done: s => `${s} yeniden bağlandı`, doneTip: 'Yeni kimlik bilgileri bu sohbette etkin', failed: s => `${s} yeniden bağlanamadı` },
cronSuggestions: { label: 'Zamanla', tip: p => `${p}” tekrar eden bir iş olabilir; zamanlayabilirsiniz`, prefix: 'Bunu zamanlanmış görev olarak ayarla:', done: 'Zamanlama için işaretlendi', doneTip: 'Mesajı gönderin; ajan görevi oluşturur' },
snippets: {
codeReview: { label: 'Kod incelemesi', description: 'Değişikliği hata, gerileme ve eksik testler açısından inceleyin.', text: 'Bu değişikliği hata, gerileme ve eksik testler açısından incele.' },
implementationPlan: { label: 'Uygulama planı', description: 'Kodu değiştirmeden önce uygulanacak adımları belirleyin.', text: 'Kodu değiştirmeden önce kısa bir uygulama planı hazırla.' },
explainThis: { label: 'Bunu açıkla', description: 'Seçilen kodun nasıl çalıştığını ve ilgili dosyaları açıklayın.', text: 'Bunun nasıl çalıştığını açıkla ve ilgili dosyaları göster.' }
}
},
statusStack: {
agents: 'Ajanlar', background: n => `${n} arka plan görevi`, goalActive: 'Hedef etkin', goalDone: 'Hedef tamamlandı', goalPaused: 'Hedef duraklatıldı',
goalWaiting: 'Hedef bekliyor', subagents: n => `${n} alt ajan`, todos: (done, total) => `Görevler ${done}/${total}`, running: 'Çalışıyor', stop: 'Durdur', dismiss: 'Kapat', exit: c => `çıkış ${c}`,
coding: {
title: 'Çalışma ağacı', noBranch: 'Dal yok', detached: 'bağımsız HEAD', clean: 'Temiz', changed: n => `${n} değişiklik`, ahead: n => `${n} commit ileride`, behind: n => `${n} commit geride`,
review: 'İncele', close: 'Kapat', openChanges: 'Değişiklikleri aç', openFile: 'Dosyayı aç', stage: 'Hazırla', unstage: 'Hazırlamayı kaldır', stageAll: 'Tümünü hazırla',
viewAsTree: 'Ağaç olarak göster', viewAsList: 'Liste olarak göster', revert: 'Geri al', revertAll: 'Tümünü geri al',
revertConfirm: 'Bu dosyadaki değişiklikler silinip son commit durumuna dönülsün mü? Bu işlem geri alınamaz.',
revertAllConfirm: 'Tüm değişiklikler silinip dosyalar son commit durumuna dönsün mü? Bu işlem geri alınamaz.',
staged: 'Hazırlanan', noChanges: 'Değişiklik yok', notRepo: 'Git deposu değil', noDiff: 'Gösterilecek fark yok', scopeUncommitted: 'Commit edilmemiş',
scopeBranch: 'Dal', scopeLastTurn: 'Son işlem', commit: 'Commit et', commitAndPush: 'Commit et ve gönder',
commitPlaceholder: shortcut => `Mesaj (commit için ${shortcut})`, generateCommitMessage: 'Commit mesajı oluştur', stopGenerating: 'Oluşturmayı durdur',
createPr: 'PR oluştur', openPr: 'PR aç', ghMissing: 'PR açmak için GitHub CLI (gh) kurup giriş yapın', agentShip: 'AITURKten PR açmasını iste',
agentShipUnavailable: 'Bu değişikliklerin ait olduğu sohbet ekranda açık değil.',
agentShipPrompt: 'Mevcut değişiklikleri incele, açıklayıcı bir conventional commit mesajıyla kaydet, dalı gönder ve bir pull request aç.',
newBranch: 'Yeni dal', branchOffFrom: base => `${base} dalından yeni dal`, switchTo: branch => `${branch} dalına geç`,
switchFailed: branch => `${branch} dalına geçilemedi`, worktrees: 'Çalışma ağaçları'
}
},
shell: {
windowControls: 'Pencere denetimleri', paneControls: 'Panel denetimleri', appControls: 'Uygulama denetimleri',
modelMenu: { search: 'Model ara', noModels: 'Model bulunamadı', editModels: 'Modelleri düzenle…', refreshModels: 'Modelleri yenile', fast: 'Hızlı' },
modelOptions: { noOptions: 'Bu model için seçenek yok', options: 'Seçenekler', thinking: 'Düşünme', fast: 'Hızlı', effort: 'Çaba', minimal: 'En az', low: 'Düşük', medium: 'Orta', high: 'Yüksek', xhigh: 'Çok yüksek', max: 'En yüksek', ultra: 'Ultra', updateFailed: 'Model seçeneği güncellenemedi', fastFailed: 'Hızlı mod güncellenemedi' },
gatewayMenu: { gateway: 'Ağ geçidi', connected: 'Bağlandı', connecting: 'Bağlanıyor', offline: 'Çevrimdışı', inferenceReady: 'Model erişimi hazır', inferenceNotReady: 'Model erişimi hazır değil', checkingInference: 'Model erişimi kontrol ediliyor', disconnected: 'Bağlantı kesildi', reconnectGateway: 'Ağ geçidine yeniden bağlan', openSystem: 'Sistem panelini aç', connection: label => `Bağlantı: ${label}`, recentActivity: 'Son etkinlik', viewAllLogs: 'Tüm günlükleri gör →', messagingPlatforms: 'Mesajlaşma platformları' },
approvalMode: { title: 'Onay modu', ariaLabel: mode => `Onay modu: ${mode}`, manual: 'Elle', manualDescription: 'Onay gerektiren işlemlerden önce sor', smart: 'Akıllı', smartDescription: 'İşlemleri otomatik değerlendir ve gerektiğinde sor', off: 'Kapalı', offDescription: 'Onay istemeden çalıştır' },
statusbar: {
unknown: 'bilinmiyor', restart: 'yeniden başlat', update: 'güncelle', updateInProgress: 'Güncelleniyor', commitsBehind: (n, branch) => `${branch} dalından ${n} commit geride`,
desktopVersion: v => `AITURK IDE v${v}`, backendVersion: v => `Ajan sunucusu v${v}`, clientLabel: v => `istemci v${v}`,
connectionSsh: h => `SSH: ${h}`, connectionRemote: h => `Uzak: ${h}`, connectionCloud: h => `Bulut: ${h}`,
connectionCloudTooltip: h => `Hermes Cloud · ${h}`, connectionSshTooltip: h => `SSH · ${h}`, connectionRemoteTooltip: h => `Uzak · ${h}`,
backendLabel: v => `sunucu v${v}`, commit: sha => `commit ${sha}`, branch: b => `dal ${b}`, closeCommandCenter: 'Komuta merkezini kapat', openCommandCenter: 'Komuta merkezini aç',
showTerminal: 'Terminali göster', hideTerminal: 'Terminali gizle', gateway: 'Ağ geçidi', gatewayReady: 'hazır', gatewayNeedsSetup: 'kurulum gerekiyor',
gatewayUnavailable: 'model erişimi yok', gatewayChecking: 'kontrol ediliyor', gatewayConnecting: 'bağlanıyor', gatewayOffline: 'çevrimdışı', gatewayRestarting: 'yeniden başlatılıyor…',
gatewayTitle: 'Ağ geçidi', customizeTitle: 'Durum çubuğunda göster', hideStatusbar: 'Durum çubuğunu gizle', resetStatusbar: 'Varsayılanlara dön',
toggleApprovalMode: 'Onaylar', toggleBackendVersion: 'Sunucu sürümü', toggleCacheHitRate: 'Önbellek isabet oranı', toggleCommandCenter: 'Komuta merkezi',
toggleContextUsage: 'Bağlam ölçer', toggleRunningTimer: 'İşlem süresi', toggleSessionTimer: 'Oturum süresi', toggleTerminal: 'Terminal', toggleTokensPerSecond: 'Saniyedeki token',
toggleVersion: 'Sürüm ve güncellemeler', toggleWorkspace: 'Çalışma alanı',
cacheHitRateTitle: 'Bu oturumun istem önbelleği isabet oranı. Önbellekteki tokenların maliyeti daha düşüktür.', tokensPerSecondTitle: 'Son 10 model çağrısındaki saniye başına ortalama çıktı tokenı',
agents: 'Ajanlar', closeAgents: 'Ajanları kapat', openAgents: 'Ajanları aç', subagents: n => `${n} alt ajan`, failed: n => `${n} başarısız`, running: n => `${n} çalışıyor`,
cron: 'Zamanlama', openCron: 'Zamanlanmış işleri aç', webhooks: 'Web kancaları', openWebhooks: 'Web kancalarını aç', starmap: 'Bellek grafiği', openStarmap: 'Bellek grafiğini aç',
turnRunning: 'Çalışıyor', contextUsage: 'Bağlam kullanımı',
systemResources: { title: 'Sistem kaynakları', loading: 'Kaynaklar…', gpuUtilization: 'GPU kullanımı', gpuMemory: 'GPU belleği', ram: 'RAM', unifiedNote: 'Birleşik bellek. GPU ve sistem aynı belleği paylaşır.', toggle: 'Sistem kaynakları' },
contextUsagePanel: { categories: { conversation: 'Sohbet', mcp: 'MCP', memory: 'Bellek', rules: 'Kurallar', skills: 'Yetenekler', subagent_definitions: 'Alt ajan tanımları', system_prompt: 'Sistem istemi', tool_definitions: 'Araç tanımları' }, empty: 'Henüz bağlam verisi yok', loading: 'Dağılım yükleniyor…', percentFull: p => `%${p} dolu`, title: 'Bağlam kullanımı', tokenSummary: (used, max) => `${used} / ${max} token` },
session: 'Oturum', yoloOn: 'YOLO açık. Tehlikeli komutlar otomatik onaylanır. Genel ayarı değiştirmek için Shift ile tıklayın.',
yoloOff: 'YOLO kapalı. Genel ayarı değiştirmek için Shift ile tıklayın.', modelNone: 'yok', noModel: 'model yok', switchModel: 'Model değiştir', openModelPicker: 'Model seçiciyi aç',
modelPinned: 'sizin tarafınızdan sabitlendi; yeni sohbetler ayarlardaki varsayılan yerine bunu kullanır', modelTitle: (p, m) => `Model · ${p}: ${m}`, providerModelTitle: (p, m) => `${p} · ${m}`
}
},
assistant: {
thread: {
loadingSession: 'Oturum yükleniyor', showEarlier: 'Önceki mesajları göster', loadingResponse: 'AITURK yanıtı yüklüyor', loadingLocalModel: m => `${m} belleğe yükleniyor`,
processingPrompt: 'İstem işleniyor', resumeWhenBackgroundDone: n => `${n} arka plan görevi bitince devam edecek`, thinking: 'Düşünüyor', thought: 'Düşünme',
thoughtBriefly: 'Kısa süre düşündü', thoughtFor: d => `${d} düşündü`, turnDuration: d => `Bu işlem ${d} sürdü`, today: time => `Bugün, ${time}`, yesterday: time => `Dün, ${time}`,
copy: 'Kopyala', refresh: 'Yenile', moreActions: 'Diğer işlemler', branchNewChat: 'Yeni sohbette dallandır', react: 'Tepki ver', dismissError: 'Hatayı kapat',
errorLayers: { auth: 'Kimlik doğrulama hatası', billing: 'Kredi bitti', disk: 'Disk dolu', endpoint: 'Özel API bağlantı hatası', gateway: 'Ağ geçidi hatası', generic: 'İşlem başarısız', provider: 'Sağlayıcı hatası', runtime: 'Yerel çalışma ortamı hatası', streaming: 'Akış bağlantısı hatası' },
errorRetry: 'Yeniden dene', errorSwitchProvider: 'Sağlayıcı değiştir', errorOpenLogs: 'Günlükleri aç', errorOpenLogsFailed: 'Günlük klasörü açılamadı',
errorOpenDesktopLogs: 'Masaüstü günlüklerini aç', errorCopyDiagnostics: 'Hata ayrıntılarını kopyala', errorSendDiagnostics: 'Tanılama gönder',
filesChanged: n => `${n} dosya değişti`, reviewChanges: 'İncele', readAloudFailed: 'Sesli okunamadı', preparingAudio: 'Ses hazırlanıyor…', stopReading: 'Okumayı durdur',
readAloud: 'Sesli oku', editMessage: 'Mesajı düzenle', expandMessage: 'Mesajı genişlet', scrollToBottom: 'En alta git', stop: 'Durdur',
restorePrevious: 'Önceki kontrol noktasına dön', restoreCheckpoint: 'Kontrol noktasını geri yükle', restoreFromHere: 'Bu istemden yeniden çalıştır',
restoreTitle: 'Bu kontrol noktasına dönülsün mü?', restoreBody: 'Bu istemden sonraki tüm mesajlar sohbetten kaldırılır ve istem buradan yeniden çalıştırılır.',
restoreConfirm: 'Geri yükle ve çalıştır', restoreNext: 'Sonraki kontrol noktasına dön', goForward: 'İleri git', sendEdited: 'Düzenlenen mesajı gönder', attachingFile: 'Ekleniyor…'
},
approval: {
gatewayDisconnected: 'Ağ geçidi bağlı değil', sendFailed: 'Onay yanıtı gönderilemedi', run: 'Çalıştır', command: 'Komut', moreOptions: 'Diğer onay seçenekleri',
allowSession: 'Bu oturum için izin ver', alwaysAllowMenu: 'Her zaman izin ver…', jumpToApproval: 'Onay gerekiyor', reject: 'Reddet',
alwaysTitle: 'Bu komuta her zaman izin verilsin mi?', alwaysDescription: p => `${p}” deseni profilinizin config.yaml dosyasındaki kalıcı izin listesine eklenir. Bu tür komutlar için bu oturumda ve sonraki oturumlarda yeniden sorulmaz.`, alwaysAllow: 'Her zaman izin ver'
},
clarify: {
notReady: 'Soru henüz hazır değil', gatewayDisconnected: 'Ağ geçidi bağlı değil', sendFailed: 'Yanıt gönderilemedi', loadingQuestion: 'Soru yükleniyor…',
other: 'Diğer (yanıtınızı yazın)', placeholder: 'Yanıtınızı yazın…', skip: 'Atla', skipped: 'Atlandı', continueLabel: 'Devam et', confirmAndContinueLabel: 'Onayla ve devam et',
answeredBadge: 'Yanıtlandı', questionProgress: (a, n) => `${n} sorunun ${a} tanesi yanıtlandı`, lateAnswer: (q, a) => `${q}” sorusuna yanıtım: ${a}`,
lateAnswerTip: 'Yanıtı devam mesajı olarak hazırla', lateAnswerHint: 'Bu soru artık yanıt beklemiyor. Bir seçeneği devam mesajı olarak gönderebilirsiniz.'
},
mcpSetup: {
installTitle: s => `${s} MCP sunucusu eklensin mi?`, enableTitle: s => `${s} MCP sunucusu etkinleştirilsin mi?`, authorizeTitle: s => `${s} MCP sunucusuna izin verilsin mi?`,
installAction: 'Kur', enableAction: 'Etkinleştir', authorizeAction: 'İzin ver', decline: 'Şimdi değil', declined: 'Reddedildi', installed: s => `${s} kuruldu`,
enabled: s => `${s} etkinleştirildi`, authorized: s => `${s} yetkilendirildi`, failed: s => `${s} kurulumu başarısız`, unanswered: 'Yanıt yok', toolCount: n => `${n} araç`,
notInCatalog: s => `${s}” MCP kataloğunda yok`, catalogSource: 'Nous tarafından onaylanan katalogdan', envRequired: 'Önce gerekli kimlik bilgilerini girin',
sendFailed: 'MCP kurulum yanıtı gönderilemedi', reloadFailed: 'Sunucu kaydedildi ancak araçlar yüklenemedi. Sonraki oturumda yüklenir.', gatewayDisconnected: 'Ağ geçidi bağlı değil'
},
tool: {
copyCode: 'Kodu kopyala', renderingImage: 'Görsel oluşturuluyor', copyOutput: 'Çıktıyı kopyala', copyCommand: 'Komutu kopyala', copyContent: 'İçeriği kopyala',
copyUrl: 'Adresi kopyala', copyResults: 'Sonuçları kopyala', copyQuery: 'Sorguyu kopyala', copyFile: 'Dosyayı kopyala', copyPath: 'Yolu kopyala', outputAlt: 'Araç çıktısı',
rawResponse: 'Ham yanıt', copyActivity: 'Etkinliği kopyala', recoveredOne: 'Bir başarısız adımdan sonra toparlandı', recoveredMany: n => `${n} başarısız adımdan sonra toparlandı`,
failedOne: 'Bir adım başarısız', failedMany: n => `${n} adım başarısız`, statusRunning: 'Çalışıyor', statusError: 'Hata', statusRecovered: 'Toparlandı', statusDone: 'Tamamlandı', memoryWriteNoted: 'Bellek yazımı kaydedildi',
actions: { read: 'Okundu', reading: 'Okunuyor', opened: 'Açıldı', opening: 'Açılıyor', failedToOpen: 'Açılamadı', searched: 'Arandı', searching: 'Aranıyor', ran: 'Çalıştırıldı', running: 'Çalışıyor', ranCode: 'Kod çalıştırıldı', runningCode: 'Kod çalışıyor' },
prefixes: { browser: 'Tarayıcı', web: 'Web' },
titleTemplates: { actionCommand: (a, c) => `${c} · ${a}`, actionQuoted: (a, v) => `${v}” · ${a}`, actionTarget: (a, v) => `${v} · ${a}`, prefixedDone: (p, a) => `${p} · ${a}`, runningPrefixedTool: (p, a) => `${p} · ${a}`, runningTool: a => a },
titles: {
browser_click: { done: 'Sayfa öğesine tıklandı', pending: 'Sayfa öğesine tıklanıyor', pendingAction: 'Tıklanıyor' },
browser_fill: { done: 'Form alanı dolduruldu', pending: 'Form alanı dolduruluyor', pendingAction: 'Dolduruluyor' },
browser_navigate: { done: 'Sayfa açıldı', pending: 'Sayfa açılıyor', pendingAction: 'Açılıyor' },
browser_snapshot: { done: 'Sayfa durumu alındı', pending: 'Sayfa durumu alınıyor', pendingAction: 'Alınıyor' },
browser_take_screenshot: { done: 'Ekran görüntüsü alındı', pending: 'Ekran görüntüsü alınıyor', pendingAction: 'Alınıyor' },
browser_type: { done: 'Sayfaya yazıldı', pending: 'Sayfaya yazılıyor', pendingAction: 'Yazılıyor' },
clarify: { done: 'Soru soruldu', pending: 'Soru soruluyor', pendingAction: 'Soruluyor' },
cronjob: { done: 'Görev zamanlandı', pending: 'Görev zamanlanıyor', pendingAction: 'Zamanlanıyor' },
edit_file: { done: 'Dosya düzenlendi', pending: 'Dosya düzenleniyor', pendingAction: 'Düzenleniyor' },
execute_code: { done: 'Kod çalıştırıldı', pending: 'Kod çalışıyor', pendingAction: 'Çalışıyor' },
image_generate: { done: 'Görsel oluşturuldu', pending: 'Görsel oluşturuluyor', pendingAction: 'Oluşturuluyor' },
list_files: { done: 'Dosyalar listelendi', pending: 'Dosyalar listeleniyor', pendingAction: 'Listeleniyor' },
memory: { done: 'Belleğe kaydedildi', pending: 'Belleğe kaydediliyor', pendingAction: 'Kaydediliyor' },
patch: { done: 'Dosya yamalandı', pending: 'Dosya yamalanıyor', pendingAction: 'Yamalanıyor' },
read_file: { done: 'Dosya okundu', pending: 'Dosya okunuyor', pendingAction: 'Okunuyor' },
search_files: { done: 'Dosyalarda arandı', pending: 'Dosyalarda aranıyor', pendingAction: 'Aranıyor' },
session_search_recall: { done: 'Oturum geçmişi arandı', pending: 'Oturum geçmişi aranıyor', pendingAction: 'Aranıyor' },
terminal: { done: 'Komut çalıştırıldı', pending: 'Komut çalışıyor', pendingAction: 'Çalışıyor' },
todo: { done: 'Görev listesi güncellendi', pending: 'Görev listesi güncelleniyor', pendingAction: 'Güncelleniyor' },
vision_analyze: { done: 'Görsel incelendi', pending: 'Görsel inceleniyor', pendingAction: 'İnceleniyor' },
web_extract: { done: 'Web içeriği alındı', pending: 'Web içeriği alınıyor', pendingAction: 'Alınıyor' },
web_search: { done: 'Webde arandı', pending: 'Webde aranıyor', pendingAction: 'Aranıyor' },
write_file: { done: 'Dosya yazıldı', pending: 'Dosya yazılıyor', pendingAction: 'Yazılıyor' }
}
}
},
preview: {
tab: 'Önizleme', closePane: 'Önizleme panelini kapat', loading: 'Önizleme yükleniyor', unavailable: 'Önizleme kullanılamıyor', opening: 'Açılıyor…', hide: 'Gizle',
openPreview: 'Önizlemeyi aç', openInBrowser: 'Tarayıcıda aç', openInExternal: 'Harici uygulamada aç', popIn: 'Panele al', popOut: 'Ayrı pencereye al',
linkHint: 'Önizleme paneli için ⌘/Ctrl ile tıklayın', source: 'KAYNAK', renderedPreview: 'ÖNİZLEME', diff: 'FARK', unknownSize: 'boyut bilinmiyor',
binaryTitle: 'Bu bir ikili dosya olabilir', binaryBody: name => `${name} önizlemesinde okunamayan metin görünebilir.`,
largeTitle: 'Bu dosya büyük', largeBody: (name, size) => `${name}: ${size}. Yalnızca ilk 512 KB gösterilir.`, previewAnyway: 'Yine de önizle', truncated: 'İlk 512 KB gösteriliyor.',
noInlineTitle: 'Satır içi önizleme yok', noInlineBody: mime => `${mime || 'Bu dosya türü'} bağlama eklenebilir.`, edit: 'Düzenle', editing: 'Düzenleniyor',
unsavedChanges: 'Kaydedilmemiş değişiklikler', saveFailed: m => `Kaydedilemedi: ${m}`, diskChangedTitle: 'Dosya diskte değişti',
diskChangedBody: 'Dosya siz açtıktan sonra değişti. Kendi sürümünüzle üzerine yazmak mı, düzenlemelerinizi silip yeniden yüklemek mi istersiniz?',
overwrite: 'Üzerine yaz', discardReload: 'Değişiklikleri sil ve yenile',
console: { deselect: 'Seçimi kaldır', select: 'Kaydı seç', copyFailed: 'Konsol çıktısı kopyalanamadı', copyEntry: 'Bu kaydı kopyala', sendEntry: 'Bu kaydı sohbete gönder', messages: n => `${n} konsol mesajı`, resize: 'Önizleme konsolunu boyutlandır', title: 'Önizleme konsolu', selected: n => `${n} seçildi`, sendToChat: 'Sohbete gönder', copySelected: 'Seçimi panoya kopyala', copyAll: 'Tümünü panoya kopyala', copy: 'Kopyala', clear: 'Temizle', empty: 'Henüz konsol mesajı yok.', promptHeader: 'Önizleme konsolu:', sentTitle: 'Sohbete gönderildi', sentMessage: n => `${n} günlük kaydı mesaj alanına eklendi` },
web: {
appFailedToBoot: 'Önizleme uygulaması başlatılamadı', serverNotFound: 'Sunucu bulunamadı',
remoteLoopback: 'Bu adres ajanın çalıştığı bilgisayarı gösteriyor. Tarayıcı paneli bu cihazda açılır; uzak geliştirme sunucusu için port yönlendirmesi veya erişilebilir bir adres gerekir.',
failedToLoad: 'Önizleme yüklenemedi', tryAgain: 'Yeniden dene', restarting: 'AITURK yeniden başlatılıyor…', askRestart: 'AITURKten sunucuyu yeniden başlatmasını iste',
lookingRestart: id => `AITURK yeniden başlatılacak önizleme sunucusunu arıyor (${id})`, restartingTitle: 'Önizleme sunucusu yeniden başlatılıyor',
restartingMessage: 'AITURK arka planda çalışıyor. İlerlemeyi önizleme konsolundan izleyin.', startRestartFailed: m => `Sunucu yeniden başlatılamadı: ${m}`, restartFailed: 'Sunucu yeniden başlatılamadı',
hideConsole: 'Önizleme konsolunu gizle', showConsole: 'Önizleme konsolunu göster', hideDevTools: 'Önizleme geliştirici araçlarını gizle', openDevTools: 'Önizleme geliştirici araçlarını aç',
goBack: 'Geri', goForward: 'İleri', reload: 'Sayfayı yenile', address: 'Adres', addressPlaceholder: 'Adres girin',
blankPageBody: 'Yukarıya bir adres yazın veya AITURKten sayfa açmasını isteyin.', finishedRestarting: m => `Önizleme sunucusu yeniden başlatıldı${m ? `: ${m}` : ''}`,
failedRestarting: m => `Sunucu yeniden başlatılamadı: ${m}`, unknownError: 'bilinmeyen hata', restartedTitle: 'Önizleme sunucusu yeniden başlatıldı',
reloadingNow: 'Önizleme yenileniyor.', restartFailedTitle: 'Önizleme yeniden başlatılamadı', restartFailedMessage: 'AITURK sunucuyu yeniden başlatamadı.',
stillWorking: 'AITURK çalışmaya devam ediyor; yeniden başlatma sonucu henüz gelmedi. Sunucu komutu ön planda çalışıyor olabilir.', workspaceReloading: 'Çalışma alanı değişti; önizleme yenileniyor',
fileChanged: url => `Dosya değişti; önizleme yenileniyor: ${url}`, filesChanged: (n, url) => `${n} dosya değişti; önizleme yenileniyor: ${url}`, watchFailed: m => `Önizleme dosyası izlenemedi: ${m}`,
moduleMimeDescription: 'Modül betikleri yanlış MIME türüyle sunuluyor. Projenin geliştirme sunucusunu çalıştırdığınızdan emin olun.', loadFailedConsole: (code, m) => `Yükleme başarısız${code ? ` (${code})` : ''}: ${m}`,
unreachableDescription: 'Önizleme sayfasına erişilemedi.', openTarget: url => `${url}`, fallbackTitle: 'Önizleme', annotate: 'İşaretle', annotateOn: 'İşaretlemeyi bitir',
annotateNeedPage: 'Önce uygulama içi tarayıcıda bir sayfa açın.', annotateFailed: 'İşaretleme modu başlatılamadı', commenting: 'Yorum ekleniyor', addComments: n => `${n} yorum ekle`,
commentPlaceholder: 'Yorum ekleyin…', commentTitle: n => `Yorum ${n}`, saveComment: 'Kaydet', cancelComment: 'Yorumu iptal et'
}
},
rightSidebar: {
aria: 'Sağ kenar çubuğu', panelsAria: 'Sağ kenar çubuğu panelleri', files: 'Dosyalar', terminal: 'Terminal',
noFolderSelected: 'Klasör seçilmedi', changeCwdTitle: 'Çalışma klasörünü değiştir', remotePickerTitle: 'Uzak klasör seç',
remotePickerDescription: 'Bağlı sunucudaki klasörlere göz atın.', remotePickerSelect: 'Klasör seç', folderTip: cwd => cwd,
openFolder: 'Klasör aç', refreshTree: 'Ağacı yenile', collapseAll: 'Tüm klasörleri daralt', previewUnavailable: 'Önizleme kullanılamıyor',
couldNotPreview: path => `${path} önizlenemedi`, noProjectTitle: 'Proje yok',
noProjectBody: 'Dosyalara göz atmak ve değişiklikleri incelemek için proje açın.', noProjectOpen: 'Açık proje yok', noDiffs: 'Değişiklik yok',
unreadableTitle: 'Okunamıyor', unreadableBody: err => `Bu klasör okunamadı (${err}).`, emptyTitle: 'Boş', emptyBody: 'Bu klasör boş.',
treeErrorTitle: 'Dosya ağacı hatası', treeErrorBody: 'Bu klasör gösterilirken hata oluştu.', tryAgain: 'Yeniden dene',
loadingTree: 'Dosya ağacı yükleniyor', loadingFiles: 'Dosyalar yükleniyor', terminalHide: 'Terminali gizle', terminalsAria: 'Terminaller',
terminalNew: 'Yeni terminal', terminalCloseOthers: 'Diğerlerini kapat', terminalCloseAll: 'Tümünü kapat', addToChat: 'Sohbete ekle'
}
})
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff