diff --git a/client/src/api/client.ts b/client/src/api/client.ts index 09e90df47..67264d9e2 100644 --- a/client/src/api/client.ts +++ b/client/src/api/client.ts @@ -315,6 +315,14 @@ export const authApi = { create: (name: string) => apiClient.post('/auth/mcp-tokens', { name } satisfies McpTokenCreateRequest).then(r => r.data), delete: (id: number) => apiClient.delete(`/auth/mcp-tokens/${id}`).then(r => r.data), }, + // Keys for the public API. Same shape as the MCP tokens above and a separate + // credential: one drives the assistant tools, the other reads trips over HTTP, + // and a key of the wrong kind is refused like one that does not exist. + apiKeys: { + list: () => apiClient.get('/auth/api-tokens').then(r => r.data), + create: (name: string) => apiClient.post('/auth/api-tokens', { name } satisfies McpTokenCreateRequest).then(r => r.data), + delete: (id: number) => apiClient.delete(`/auth/api-tokens/${id}`).then(r => r.data), + }, passkey: { registerOptions: (password: string) => apiClient.post('/auth/passkey/register/options', { password }).then(r => r.data), registerVerify: (attestationResponse: unknown, name?: string) => apiClient.post('/auth/passkey/register/verify', { attestationResponse, name }).then(r => r.data), diff --git a/client/src/components/Settings/ApiKeysSection.test.tsx b/client/src/components/Settings/ApiKeysSection.test.tsx new file mode 100644 index 000000000..cc1067bc9 --- /dev/null +++ b/client/src/components/Settings/ApiKeysSection.test.tsx @@ -0,0 +1,228 @@ +// FE-COMP-APIKEYS-001 to FE-COMP-APIKEYS-010 +import { render, screen, waitFor } from '../../../tests/helpers/render'; +import userEvent from '@testing-library/user-event'; +import { http, HttpResponse } from 'msw'; +import { server } from '../../../tests/helpers/msw/server'; +import { useAuthStore } from '../../store/authStore'; +import { useAddonStore } from '../../store/addonStore'; +import { resetAllStores, seedStore } from '../../../tests/helpers/store'; +import { buildUser } from '../../../tests/helpers/factories'; +import { ToastContainer } from '../shared/Toast'; +import ApiKeysSection from './ApiKeysSection'; + +const clipboardWriteText = vi.fn().mockResolvedValue(undefined); + +beforeAll(() => { + Object.defineProperty(navigator, 'clipboard', { + value: { writeText: clipboardWriteText }, + configurable: true, + writable: true, + }); +}); + +beforeEach(() => { + clipboardWriteText.mockClear(); + resetAllStores(); + vi.clearAllMocks(); + seedStore(useAuthStore, { user: buildUser(), isAuthenticated: true }); + seedStore(useAddonStore, { addons: [], loaded: true, loadAddons: vi.fn() }); + server.use(http.get('/api/auth/api-tokens', () => HttpResponse.json({ tokens: [] }))); +}); + +function renderSection() { + return render( + <> + + + , + ); +} + +describe('ApiKeysSection', () => { + it('FE-COMP-APIKEYS-001: renders without the MCP addon, because an API key does not need it', async () => { + renderSection(); + expect(await screen.findByText('API Keys')).toBeInTheDocument(); + }); + + it('FE-COMP-APIKEYS-002: reads its own endpoint, not the MCP token list', async () => { + const seen: string[] = []; + server.use( + http.get('/api/auth/api-tokens', ({ request }) => { + seen.push(new URL(request.url).pathname); + return HttpResponse.json({ tokens: [] }); + }), + http.get('/api/auth/mcp-tokens', ({ request }) => { + seen.push(new URL(request.url).pathname); + return HttpResponse.json({ tokens: [] }); + }), + ); + renderSection(); + await waitFor(() => expect(seen).toContain('/api/auth/api-tokens')); + expect(seen).not.toContain('/api/auth/mcp-tokens'); + }); + + it('FE-COMP-APIKEYS-003: lists existing keys by prefix, never the key itself', async () => { + server.use( + http.get('/api/auth/api-tokens', () => + HttpResponse.json({ + tokens: [ + { + id: 7, + name: 'Dawarich', + token_prefix: 'trek_abcdefg', + created_at: '2026-08-01T10:00:00Z', + last_used_at: null, + }, + ], + }), + ), + ); + renderSection(); + expect(await screen.findByText('Dawarich')).toBeInTheDocument(); + expect(screen.getByText(/trek_abcdefg\.\.\./)).toBeInTheDocument(); + }); + + it('FE-COMP-APIKEYS-004: shows the empty state when there is nothing to list', async () => { + renderSection(); + expect(await screen.findByText(/No keys yet/)).toBeInTheDocument(); + }); + + it('FE-COMP-APIKEYS-005: creates a key and shows it once, with the warning', async () => { + server.use( + http.post('/api/auth/api-tokens', () => + HttpResponse.json({ + token: { + id: 1, + name: 'Dawarich', + raw_token: 'trek_thefullsecretvalue', + token_prefix: 'trek_thefull', + created_at: '2026-08-27T10:00:00Z', + }, + }), + ), + ); + const user = userEvent.setup(); + renderSection(); + + await user.click(await screen.findByRole('button', { name: /Create key/ })); + await user.type(screen.getByPlaceholderText('e.g. Dawarich'), 'Dawarich'); + await user.click(screen.getByRole('button', { name: /^Create$/ })); + + expect(await screen.findByText('trek_thefullsecretvalue')).toBeInTheDocument(); + expect(screen.getByText(/shown once/)).toBeInTheDocument(); + }); + + it('FE-COMP-APIKEYS-006: drops the raw key from the DOM once the modal is closed', async () => { + server.use( + http.post('/api/auth/api-tokens', () => + HttpResponse.json({ + token: { + id: 1, + name: 'Dawarich', + raw_token: 'trek_thefullsecretvalue', + token_prefix: 'trek_thefull', + created_at: '2026-08-27T10:00:00Z', + }, + }), + ), + ); + const user = userEvent.setup(); + renderSection(); + + await user.click(await screen.findByRole('button', { name: /Create key/ })); + await user.type(screen.getByPlaceholderText('e.g. Dawarich'), 'Dawarich'); + await user.click(screen.getByRole('button', { name: /^Create$/ })); + await screen.findByText('trek_thefullsecretvalue'); + await user.click(screen.getByRole('button', { name: /Done/ })); + + await waitFor(() => expect(screen.queryByText('trek_thefullsecretvalue')).not.toBeInTheDocument()); + // The list keeps only the prefix, which is what the server stores alongside the hash. + expect(screen.getByText(/trek_thefull\.\.\./)).toBeInTheDocument(); + }); + + it('FE-COMP-APIKEYS-007: refuses to create a key without a name', async () => { + const user = userEvent.setup(); + renderSection(); + await user.click(await screen.findByRole('button', { name: /Create key/ })); + expect(screen.getByRole('button', { name: /^Create$/ })).toBeDisabled(); + }); + + it('FE-COMP-APIKEYS-008: asks before deleting, and only deletes on confirm', async () => { + let deleted = 0; + server.use( + http.get('/api/auth/api-tokens', () => + HttpResponse.json({ + tokens: [ + { id: 7, name: 'Dawarich', token_prefix: 'trek_abc', created_at: '2026-08-01T10:00:00Z', last_used_at: null }, + ], + }), + ), + http.delete('/api/auth/api-tokens/7', () => { + deleted += 1; + return HttpResponse.json({ success: true }); + }), + ); + const user = userEvent.setup(); + renderSection(); + + await user.click(await screen.findByTitle('Delete key')); + expect(deleted).toBe(0); + expect(screen.getByText(/stops working immediately/)).toBeInTheDocument(); + + await user.click(screen.getByRole('button', { name: /^Cancel$/ })); + expect(deleted).toBe(0); + + await user.click(await screen.findByTitle('Delete key')); + const [, confirm] = screen.getAllByRole('button', { name: /Delete key/ }); + await user.click(confirm); + await waitFor(() => expect(deleted).toBe(1)); + await waitFor(() => expect(screen.queryByText('Dawarich')).not.toBeInTheDocument()); + }); + + it('FE-COMP-APIKEYS-009: reports a failed creation instead of pretending it worked', async () => { + server.use(http.post('/api/auth/api-tokens', () => HttpResponse.json({ error: 'nope' }, { status: 500 }))); + const user = userEvent.setup(); + renderSection(); + + await user.click(await screen.findByRole('button', { name: /Create key/ })); + await user.type(screen.getByPlaceholderText('e.g. Dawarich'), 'Dawarich'); + await user.click(screen.getByRole('button', { name: /^Create$/ })); + + expect(await screen.findByText(/Could not create the key/)).toBeInTheDocument(); + }); + + it('FE-COMP-APIKEYS-010: copies the new key to the clipboard on request', async () => { + server.use( + http.post('/api/auth/api-tokens', () => + HttpResponse.json({ + token: { + id: 1, + name: 'Dawarich', + raw_token: 'trek_thefullsecretvalue', + token_prefix: 'trek_thefull', + created_at: '2026-08-27T10:00:00Z', + }, + }), + ), + ); + const user = userEvent.setup(); + // userEvent.setup() installs its own clipboard stub, so the spy goes back on + // afterwards — otherwise this asserts against a stub the component never reaches. + Object.defineProperty(navigator, 'clipboard', { + value: { writeText: clipboardWriteText }, + configurable: true, + writable: true, + }); + renderSection(); + + await user.click(await screen.findByRole('button', { name: /Create key/ })); + await user.type(screen.getByPlaceholderText('e.g. Dawarich'), 'Dawarich'); + await user.click(screen.getByRole('button', { name: /^Create$/ })); + await screen.findByText('trek_thefullsecretvalue'); + await user.click(screen.getByTitle('Copy')); + + expect(clipboardWriteText).toHaveBeenCalledWith('trek_thefullsecretvalue'); + // And the button confirms it to the reader, which is the only feedback there is. + expect(await screen.findByTitle('Copy')).toBeInTheDocument(); + }); +}); diff --git a/client/src/components/Settings/ApiKeysSection.tsx b/client/src/components/Settings/ApiKeysSection.tsx new file mode 100644 index 000000000..b33be69f2 --- /dev/null +++ b/client/src/components/Settings/ApiKeysSection.tsx @@ -0,0 +1,208 @@ +import React, { useEffect, useState } from 'react' +import { KeyRound, Plus, Trash2, Copy, Check } from 'lucide-react' +import Section from './Section' +import { useTranslation } from '../../i18n' +import { useToast } from '../shared/Toast' +import { authApi } from '../../api/client' + +/** + * Keys for the public API — the credential a user hands to other software that + * should read their trips. + * + * Its own section rather than a third tab under MCP: an API key is not an MCP + * credential, it does not need the MCP addon, and burying it under a heading + * about AI assistants is how people end up minting the wrong kind. The two look + * alike deliberately (name, prefix, shown once) because they are the same + * gesture; what differs is which door they open. + * + * State lives here rather than in the tab's shared hook so the section works on + * an instance with MCP switched off. + */ +interface ApiKey { + id: number + name: string + token_prefix: string + created_at: string + last_used_at: string | null +} + +export default function ApiKeysSection(): React.ReactElement { + const { t, locale } = useTranslation() + const toast = useToast() + const [keys, setKeys] = useState([]) + const [modalOpen, setModalOpen] = useState(false) + const [newName, setNewName] = useState('') + const [creating, setCreating] = useState(false) + /** The raw key, held only until the modal closes — the server keeps a hash. */ + const [created, setCreated] = useState(null) + const [deleteId, setDeleteId] = useState(null) + const [copied, setCopied] = useState(false) + + useEffect(() => { + authApi.apiKeys.list().then(d => setKeys(d.tokens || [])).catch(() => {}) + }, []) + + const handleCreate = async () => { + if (!newName.trim() || creating) return + setCreating(true) + try { + const d = await authApi.apiKeys.create(newName.trim()) + setCreated(d.token.raw_token) + setKeys(prev => [ + { id: d.token.id, name: d.token.name, token_prefix: d.token.token_prefix, created_at: d.token.created_at, last_used_at: null }, + ...prev, + ]) + setNewName('') + } catch { + toast.error(t('settings.apiKeys.createFailed')) + } finally { + setCreating(false) + } + } + + const handleDelete = async (id: number) => { + try { + await authApi.apiKeys.delete(id) + setKeys(prev => prev.filter(k => k.id !== id)) + toast.success(t('settings.apiKeys.deleted')) + } catch { + toast.error(t('settings.apiKeys.deleteFailed')) + } finally { + setDeleteId(null) + } + } + + const handleCopy = () => { + if (!created) return + navigator.clipboard.writeText(created) + setCopied(true) + setTimeout(() => setCopied(false), 2000) + } + + const closeModal = () => { + setModalOpen(false) + setCreated(null) + setNewName('') + } + + return ( + <> +
+

{t('settings.apiKeys.description')}

+ +
+ +
+ + {keys.length === 0 ? ( +

+ {t('settings.apiKeys.empty')} +

+ ) : ( +
+ {keys.map((key, i) => ( +
+
+

{key.name}

+

+ {key.token_prefix}... + {t('settings.apiKeys.createdAt')} {new Date(key.created_at).toLocaleDateString(locale)} + {key.last_used_at && ( + · {t('settings.apiKeys.usedAt')} {new Date(key.last_used_at).toLocaleDateString(locale)} + )} +

+
+ +
+ ))} +
+ )} + +

{t('settings.apiKeys.docsHint')}

+
+ + {modalOpen && ( +
{ if (e.target === e.currentTarget && !created) closeModal() }}> +
+ {!created ? ( + <> +

{t('settings.apiKeys.modal.createTitle')}

+
+ + setNewName(e.target.value)} + onKeyDown={e => e.key === 'Enter' && handleCreate()} + placeholder={t('settings.apiKeys.modal.namePlaceholder')} + className="w-full px-3 py-2.5 border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-slate-400 border-edge bg-surface-secondary text-content" + autoFocus /> +

{t('settings.apiKeys.modal.nameHint')}

+
+
+ + +
+ + ) : ( + <> +

{t('settings.apiKeys.modal.createdTitle')}

+
+ ⚠ +

{t('settings.apiKeys.modal.createdWarning')}

+
+
+
+                    {created}
+                  
+ +
+
+ +
+ + )} +
+
+ )} + + {deleteId !== null && ( +
{ if (e.target === e.currentTarget) setDeleteId(null) }}> +
+

{t('settings.apiKeys.deleteTitle')}

+

{t('settings.apiKeys.deleteMessage')}

+
+ + +
+
+
+ )} + + ) +} diff --git a/client/src/components/Settings/IntegrationsTab.tsx b/client/src/components/Settings/IntegrationsTab.tsx index b0aaaac38..965130177 100644 --- a/client/src/components/Settings/IntegrationsTab.tsx +++ b/client/src/components/Settings/IntegrationsTab.tsx @@ -8,6 +8,7 @@ import { useAddonStore } from '../../store/addonStore' import PhotoProvidersSection from './PhotoProvidersSection' import AirTrailConnectionSection from './AirTrailConnectionSection' import LlmConnectionSection from './LlmConnectionSection' +import ApiKeysSection from './ApiKeysSection' import { ALL_SCOPES } from '../../api/oauthScopes' import ScopeGroupPicker from '../OAuth/ScopeGroupPicker' import { useAuthStore } from '../../store/authStore' @@ -109,6 +110,9 @@ export default function IntegrationsTab(): React.ReactElement { a managed install. The per-user fallback exists for people who supply their own key, and there nobody does. */} {S.llmEnabled && !managed && } + {/* Above MCP on purpose: an API key needs no addon, and someone looking for + one should not have to read past a section about AI assistants. */} + {S.mcpEnabled && } diff --git a/server/tests/unit/nest/token.service.test.ts b/server/tests/unit/nest/token.service.test.ts index 2c87dddf2..f0383b815 100644 --- a/server/tests/unit/nest/token.service.test.ts +++ b/server/tests/unit/nest/token.service.test.ts @@ -150,6 +150,92 @@ describe('MCP token service', () => { }); }); +// --------------------------------------------------------------------------- +// API keys — the same table, a different door +// +// An MCP token drives every assistant tool; an API key reads trips over HTTP. +// The kind is in the WHERE clause of every lookup, so these tests are the ones +// that would catch a credential quietly opening the wrong surface. +// --------------------------------------------------------------------------- + +describe('API key service', () => { + it('TOKEN-010: an API key does not verify as an MCP token', () => { + const { user } = createUser(testDb); + const created = svc.createApiToken(user.id, 'dawarich'); + const raw = (created.token as { raw_token: string }).raw_token; + + expect(svc.verifyApiToken(raw)?.id).toBe(user.id); + expect(svc.verifyMcpToken(raw)).toBeNull(); + }); + + it('TOKEN-011: an MCP token does not verify as an API key', () => { + const { user } = createUser(testDb); + const created = svc.createMcpToken(user.id, 'claude'); + const raw = (created.token as { raw_token: string }).raw_token; + + expect(svc.verifyMcpToken(raw)?.id).toBe(user.id); + expect(svc.verifyApiToken(raw)).toBeNull(); + }); + + it('TOKEN-012: each list shows only its own kind', () => { + const { user } = createUser(testDb); + svc.createMcpToken(user.id, 'claude'); + svc.createApiToken(user.id, 'dawarich'); + + const mcp = svc.listMcpTokens(user.id) as Record[]; + const api = svc.listApiTokens(user.id) as Record[]; + expect(mcp.map((t) => t.name)).toEqual(['claude']); + expect(api.map((t) => t.name)).toEqual(['dawarich']); + }); + + it('TOKEN-013: deleting across kinds 404s, identically to an unknown id', () => { + const { user } = createUser(testDb); + const mcpId = String((svc.createMcpToken(user.id, 'claude').token as { id: number }).id); + const apiId = String((svc.createApiToken(user.id, 'dawarich').token as { id: number }).id); + + expect(svc.deleteApiToken(user.id, mcpId)).toEqual({ error: 'Token not found', status: 404 }); + expect(svc.deleteMcpToken(user.id, apiId)).toEqual({ error: 'Token not found', status: 404 }); + // Neither row was touched: a wrong-kind delete must not be a way to revoke + // someone's assistant access from the API-key screen. + expect(testDb.prepare('SELECT id FROM mcp_tokens WHERE id = ?').get(mcpId)).toBeDefined(); + expect(testDb.prepare('SELECT id FROM mcp_tokens WHERE id = ?').get(apiId)).toBeDefined(); + }); + + it('TOKEN-014: the ten-token ceiling counts each kind on its own', () => { + const { user } = createUser(testDb); + for (let i = 0; i < 10; i += 1) svc.createMcpToken(user.id, `mcp-${i}`); + + expect(svc.createMcpToken(user.id, 'one-too-many').status).toBe(400); + // A full MCP shelf must not lock the user out of minting an API key. + expect(svc.createApiToken(user.id, 'dawarich').status).toBeUndefined(); + }); + + it('TOKEN-015: an API key is stored hashed, with only a prefix in the clear', () => { + const { user } = createUser(testDb); + const created = svc.createApiToken(user.id, 'dawarich'); + const raw = (created.token as { raw_token: string }).raw_token; + + const row = testDb + .prepare('SELECT token_hash, token_prefix, kind FROM mcp_tokens WHERE user_id = ?') + .get(user.id) as { token_hash: string; token_prefix: string; kind: string }; + expect(row.kind).toBe('api'); + expect(row.token_hash).not.toBe(raw); + expect(raw.startsWith(row.token_prefix)).toBe(true); + }); + + it('TOKEN-016: verifying an API key records last_used_at, so a stale key is visible', () => { + const { user } = createUser(testDb); + const raw = (svc.createApiToken(user.id, 'dawarich').token as { raw_token: string }).raw_token; + + const before = svc.listApiTokens(user.id) as Record[]; + expect(before[0].last_used_at).toBeNull(); + + svc.verifyApiToken(raw); + const after = svc.listApiTokens(user.id) as Record[]; + expect(after[0].last_used_at).not.toBeNull(); + }); +}); + // --------------------------------------------------------------------------- // MCP tokens — the admin half // diff --git a/shared/src/i18n/ar/settings.ts b/shared/src/i18n/ar/settings.ts index d3124b829..707e3e6c1 100644 --- a/shared/src/i18n/ar/settings.ts +++ b/shared/src/i18n/ar/settings.ts @@ -516,6 +516,30 @@ const settings: TranslationStrings = { 'settings.alwaysShowRoutes': 'إظهار مسارات الحجوزات دائمًا', 'settings.alwaysShowRoutesHint': 'يعرض تلقائيًا مسار كل رحلة طيران وقطار وحجز آخر على الخريطة، دون الحاجة إلى تفعيله لكل عنصر على حدة.', + + // Public API keys (Settings -> Integrations) + 'settings.apiKeys.title': 'مفاتيح API', + 'settings.apiKeys.description': 'مفاتيح لواجهة API العامة، حتى تتمكن برامج أخرى من قراءة رحلاتك. للقراءة فقط: لا يمكن للمفتاح تغيير أي شيء أو حذفه.', + 'settings.apiKeys.create': 'إنشاء مفتاح', + 'settings.apiKeys.empty': 'لا توجد مفاتيح بعد. أنشئ مفتاحًا لربط برامج أخرى.', + 'settings.apiKeys.createdAt': 'أُنشئ', + 'settings.apiKeys.usedAt': 'آخر استخدام', + 'settings.apiKeys.deleteTitle': 'حذف المفتاح', + 'settings.apiKeys.deleteMessage': 'كل ما يستخدم هذا المفتاح سيتوقف فورًا. لا يمكن التراجع عن ذلك.', + 'settings.apiKeys.deleted': 'تم حذف المفتاح', + 'settings.apiKeys.deleteFailed': 'تعذّر حذف المفتاح', + 'settings.apiKeys.createFailed': 'تعذّر إنشاء المفتاح', + 'settings.apiKeys.copy': 'نسخ', + 'settings.apiKeys.docsHint': 'أرسل المفتاح بصيغة "Authorization: Bearer ..." أو "X-API-Key: ..." إلى /api/v1.', + 'settings.apiKeys.modal.createTitle': 'إنشاء مفتاح API', + 'settings.apiKeys.modal.name': 'الاسم', + 'settings.apiKeys.modal.namePlaceholder': 'مثال: Dawarich', + 'settings.apiKeys.modal.nameHint': 'لك وحدك، لتتعرّف على المفتاح لاحقًا.', + 'settings.apiKeys.modal.creating': 'جارٍ الإنشاء...', + 'settings.apiKeys.modal.create': 'إنشاء', + 'settings.apiKeys.modal.createdTitle': 'تم إنشاء مفتاح API', + 'settings.apiKeys.modal.createdWarning': 'انسخ المفتاح الآن. يُعرض مرة واحدة فقط ولا يمكن استرجاعه لاحقًا.', + 'settings.apiKeys.modal.done': 'تم', }; export default settings; diff --git a/shared/src/i18n/br/settings.ts b/shared/src/i18n/br/settings.ts index e777359cd..2e20f3abd 100644 --- a/shared/src/i18n/br/settings.ts +++ b/shared/src/i18n/br/settings.ts @@ -532,6 +532,30 @@ const settings: TranslationStrings = { 'settings.alwaysShowRoutes': 'Sempre mostrar rotas de reserva', 'settings.alwaysShowRoutesHint': 'Mostra automaticamente no mapa a rota de cada voo, trem e outra reserva, sem precisar ativar item por item.', + + // Public API keys (Settings -> Integrations) + 'settings.apiKeys.title': 'Chaves de API', + 'settings.apiKeys.description': 'Chaves para a API pública, para que outros softwares possam ler suas viagens. Somente leitura: uma chave não pode alterar nem excluir nada.', + 'settings.apiKeys.create': 'Criar chave', + 'settings.apiKeys.empty': 'Nenhuma chave ainda. Crie uma para conectar outros softwares.', + 'settings.apiKeys.createdAt': 'criada', + 'settings.apiKeys.usedAt': 'último uso', + 'settings.apiKeys.deleteTitle': 'Excluir chave', + 'settings.apiKeys.deleteMessage': 'Tudo que usa esta chave para de funcionar imediatamente. Não é possível desfazer.', + 'settings.apiKeys.deleted': 'Chave excluída', + 'settings.apiKeys.deleteFailed': 'Não foi possível excluir a chave', + 'settings.apiKeys.createFailed': 'Não foi possível criar a chave', + 'settings.apiKeys.copy': 'Copiar', + 'settings.apiKeys.docsHint': 'Envie a chave como "Authorization: Bearer ..." ou "X-API-Key: ..." para /api/v1.', + 'settings.apiKeys.modal.createTitle': 'Criar chave de API', + 'settings.apiKeys.modal.name': 'Nome', + 'settings.apiKeys.modal.namePlaceholder': 'ex.: Dawarich', + 'settings.apiKeys.modal.nameHint': 'Apenas para você, para reconhecer a chave depois.', + 'settings.apiKeys.modal.creating': 'Criando...', + 'settings.apiKeys.modal.create': 'Criar', + 'settings.apiKeys.modal.createdTitle': 'Chave de API criada', + 'settings.apiKeys.modal.createdWarning': 'Copie a chave agora. Ela é exibida uma única vez e não pode ser recuperada depois.', + 'settings.apiKeys.modal.done': 'Concluído', }; export default settings; diff --git a/shared/src/i18n/ca/settings.ts b/shared/src/i18n/ca/settings.ts index a45c956a2..9f3fe894e 100644 --- a/shared/src/i18n/ca/settings.ts +++ b/shared/src/i18n/ca/settings.ts @@ -535,6 +535,30 @@ const settings: TranslationStrings = { 'settings.alwaysShowRoutes': 'Mostra sempre les rutes de reserva', 'settings.alwaysShowRoutesHint': 'Dibuixa automàticament al mapa la ruta de cada vol, tren i altra reserva — no cal activar-la una per una.', + + // Public API keys (Settings -> Integrations) + 'settings.apiKeys.title': 'Claus API', + 'settings.apiKeys.description': 'Claus per a l\'API pública, perquè altres programes puguin llegir els teus viatges. Només lectura: una clau no pot canviar ni esborrar res.', + 'settings.apiKeys.create': 'Crea una clau', + 'settings.apiKeys.empty': 'Encara no hi ha claus. Crea\'n una per connectar altres programes.', + 'settings.apiKeys.createdAt': 'creada', + 'settings.apiKeys.usedAt': 'últim ús', + 'settings.apiKeys.deleteTitle': 'Elimina la clau', + 'settings.apiKeys.deleteMessage': 'Tot el que faci servir aquesta clau deixarà de funcionar immediatament. Això no es pot desfer.', + 'settings.apiKeys.deleted': 'Clau eliminada', + 'settings.apiKeys.deleteFailed': 'No s\'ha pogut eliminar la clau', + 'settings.apiKeys.createFailed': 'No s\'ha pogut crear la clau', + 'settings.apiKeys.copy': 'Copia', + 'settings.apiKeys.docsHint': 'Envia la clau com a "Authorization: Bearer ..." o "X-API-Key: ..." a /api/v1.', + 'settings.apiKeys.modal.createTitle': 'Crea una clau API', + 'settings.apiKeys.modal.name': 'Nom', + 'settings.apiKeys.modal.namePlaceholder': 'p. ex. Dawarich', + 'settings.apiKeys.modal.nameHint': 'Només per a tu, perquè reconeguis la clau més endavant.', + 'settings.apiKeys.modal.creating': 'S\'està creant...', + 'settings.apiKeys.modal.create': 'Crea', + 'settings.apiKeys.modal.createdTitle': 'Clau API creada', + 'settings.apiKeys.modal.createdWarning': 'Copia la clau ara. Només es mostra un cop i no es pot recuperar després.', + 'settings.apiKeys.modal.done': 'Fet', }; export default settings; diff --git a/shared/src/i18n/cs/settings.ts b/shared/src/i18n/cs/settings.ts index 6b6c78623..c383b3b86 100644 --- a/shared/src/i18n/cs/settings.ts +++ b/shared/src/i18n/cs/settings.ts @@ -524,6 +524,30 @@ const settings: TranslationStrings = { 'settings.alwaysShowRoutes': 'Vždy zobrazovat trasy rezervací', 'settings.alwaysShowRoutesHint': 'Automaticky zobrazí na mapě trasu každého letu, vlaku a jiné rezervace, aniž byste ji museli zapínat jednotlivě.', + + // Public API keys (Settings -> Integrations) + 'settings.apiKeys.title': 'Klíče API', + 'settings.apiKeys.description': 'Klíče pro veřejné API, aby jiný software mohl číst tvoje cesty. Jen pro čtení: klíč nemůže nic měnit ani mazat.', + 'settings.apiKeys.create': 'Vytvořit klíč', + 'settings.apiKeys.empty': 'Zatím žádné klíče. Vytvoř jeden a připoj další software.', + 'settings.apiKeys.createdAt': 'vytvořen', + 'settings.apiKeys.usedAt': 'naposledy použit', + 'settings.apiKeys.deleteTitle': 'Smazat klíč', + 'settings.apiKeys.deleteMessage': 'Vše, co tento klíč používá, okamžitě přestane fungovat. Akci nelze vrátit zpět.', + 'settings.apiKeys.deleted': 'Klíč smazán', + 'settings.apiKeys.deleteFailed': 'Klíč se nepodařilo smazat', + 'settings.apiKeys.createFailed': 'Klíč se nepodařilo vytvořit', + 'settings.apiKeys.copy': 'Kopírovat', + 'settings.apiKeys.docsHint': 'Pošli klíč jako "Authorization: Bearer ..." nebo "X-API-Key: ..." na /api/v1.', + 'settings.apiKeys.modal.createTitle': 'Vytvořit klíč API', + 'settings.apiKeys.modal.name': 'Název', + 'settings.apiKeys.modal.namePlaceholder': 'např. Dawarich', + 'settings.apiKeys.modal.nameHint': 'Jen pro tebe, abys klíč později poznal.', + 'settings.apiKeys.modal.creating': 'Vytváří se...', + 'settings.apiKeys.modal.create': 'Vytvořit', + 'settings.apiKeys.modal.createdTitle': 'Klíč API vytvořen', + 'settings.apiKeys.modal.createdWarning': 'Zkopíruj klíč hned teď. Zobrazí se jen jednou a později ho už nelze získat.', + 'settings.apiKeys.modal.done': 'Hotovo', }; export default settings; diff --git a/shared/src/i18n/de/settings.ts b/shared/src/i18n/de/settings.ts index 04035ab1e..4b56f0383 100644 --- a/shared/src/i18n/de/settings.ts +++ b/shared/src/i18n/de/settings.ts @@ -535,6 +535,30 @@ const settings: TranslationStrings = { 'settings.alwaysShowRoutes': 'Buchungsrouten immer anzeigen', 'settings.alwaysShowRoutesHint': 'Zeigt die Route für jeden Flug, jede Zugfahrt und jede andere Buchung automatisch auf der Karte an – ohne sie einzeln aktivieren zu müssen.', + + // Public API keys (Settings -> Integrations) + 'settings.apiKeys.title': 'API-Schlüssel', + 'settings.apiKeys.description': 'Schlüssel für die öffentliche API, damit andere Software deine Reisen lesen kann. Nur lesend: ein Schlüssel kann nichts ändern oder löschen.', + 'settings.apiKeys.create': 'Schlüssel erstellen', + 'settings.apiKeys.empty': 'Noch keine Schlüssel. Erstelle einen, um andere Software zu verbinden.', + 'settings.apiKeys.createdAt': 'erstellt', + 'settings.apiKeys.usedAt': 'zuletzt genutzt', + 'settings.apiKeys.deleteTitle': 'Schlüssel löschen', + 'settings.apiKeys.deleteMessage': 'Alles, was diesen Schlüssel nutzt, hört sofort auf zu funktionieren. Das lässt sich nicht rückgängig machen.', + 'settings.apiKeys.deleted': 'Schlüssel gelöscht', + 'settings.apiKeys.deleteFailed': 'Schlüssel konnte nicht gelöscht werden', + 'settings.apiKeys.createFailed': 'Schlüssel konnte nicht erstellt werden', + 'settings.apiKeys.copy': 'Kopieren', + 'settings.apiKeys.docsHint': 'Schicke den Schlüssel als "Authorization: Bearer ..." oder "X-API-Key: ..." an /api/v1.', + 'settings.apiKeys.modal.createTitle': 'API-Schlüssel erstellen', + 'settings.apiKeys.modal.name': 'Name', + 'settings.apiKeys.modal.namePlaceholder': 'z. B. Dawarich', + 'settings.apiKeys.modal.nameHint': 'Nur für dich, damit du den Schlüssel später wiedererkennst.', + 'settings.apiKeys.modal.creating': 'Wird erstellt...', + 'settings.apiKeys.modal.create': 'Erstellen', + 'settings.apiKeys.modal.createdTitle': 'API-Schlüssel erstellt', + 'settings.apiKeys.modal.createdWarning': 'Kopiere den Schlüssel jetzt. Er wird nur einmal angezeigt und lässt sich später nicht mehr abrufen.', + 'settings.apiKeys.modal.done': 'Fertig', }; export default settings; diff --git a/shared/src/i18n/en/settings.ts b/shared/src/i18n/en/settings.ts index 6c9335f65..95bc51df4 100644 --- a/shared/src/i18n/en/settings.ts +++ b/shared/src/i18n/en/settings.ts @@ -522,6 +522,30 @@ const settings: TranslationStrings = { 'settings.alwaysShowRoutes': 'Always show booking routes', 'settings.alwaysShowRoutesHint': 'Automatically draw the route for every flight, train and other booking on the map — no need to switch it on per item.', + + // Public API keys (Settings -> Integrations) + 'settings.apiKeys.title': 'API Keys', + 'settings.apiKeys.description': 'Keys for the public API, so other software can read your trips. Read-only: a key cannot change or delete anything.', + 'settings.apiKeys.create': 'Create key', + 'settings.apiKeys.empty': 'No keys yet. Create one to connect other software.', + 'settings.apiKeys.createdAt': 'created', + 'settings.apiKeys.usedAt': 'last used', + 'settings.apiKeys.deleteTitle': 'Delete key', + 'settings.apiKeys.deleteMessage': 'Anything using this key stops working immediately. This cannot be undone.', + 'settings.apiKeys.deleted': 'Key deleted', + 'settings.apiKeys.deleteFailed': 'Could not delete the key', + 'settings.apiKeys.createFailed': 'Could not create the key', + 'settings.apiKeys.copy': 'Copy', + 'settings.apiKeys.docsHint': 'Send the key as "Authorization: Bearer ..." or "X-API-Key: ..." to /api/v1.', + 'settings.apiKeys.modal.createTitle': 'Create API key', + 'settings.apiKeys.modal.name': 'Name', + 'settings.apiKeys.modal.namePlaceholder': 'e.g. Dawarich', + 'settings.apiKeys.modal.nameHint': 'Only for you, so you recognise the key later.', + 'settings.apiKeys.modal.creating': 'Creating...', + 'settings.apiKeys.modal.create': 'Create', + 'settings.apiKeys.modal.createdTitle': 'API key created', + 'settings.apiKeys.modal.createdWarning': 'Copy the key now. It is shown once and cannot be retrieved later.', + 'settings.apiKeys.modal.done': 'Done', }; export default settings; diff --git a/shared/src/i18n/es/settings.ts b/shared/src/i18n/es/settings.ts index a973e7a4e..457893af3 100644 --- a/shared/src/i18n/es/settings.ts +++ b/shared/src/i18n/es/settings.ts @@ -533,6 +533,30 @@ const settings: TranslationStrings = { 'settings.alwaysShowRoutes': 'Mostrar siempre las rutas de reserva', 'settings.alwaysShowRoutesHint': 'Muestra automáticamente en el mapa la ruta de cada vuelo, tren y otra reserva, sin necesidad de activarla una por una.', + + // Public API keys (Settings -> Integrations) + 'settings.apiKeys.title': 'Claves API', + 'settings.apiKeys.description': 'Claves para la API pública, para que otro software pueda leer tus viajes. Solo lectura: una clave no puede cambiar ni borrar nada.', + 'settings.apiKeys.create': 'Crear clave', + 'settings.apiKeys.empty': 'Todavía no hay claves. Crea una para conectar otro software.', + 'settings.apiKeys.createdAt': 'creada', + 'settings.apiKeys.usedAt': 'último uso', + 'settings.apiKeys.deleteTitle': 'Eliminar clave', + 'settings.apiKeys.deleteMessage': 'Todo lo que use esta clave dejará de funcionar de inmediato. Esto no se puede deshacer.', + 'settings.apiKeys.deleted': 'Clave eliminada', + 'settings.apiKeys.deleteFailed': 'No se pudo eliminar la clave', + 'settings.apiKeys.createFailed': 'No se pudo crear la clave', + 'settings.apiKeys.copy': 'Copiar', + 'settings.apiKeys.docsHint': 'Envía la clave como "Authorization: Bearer ..." o "X-API-Key: ..." a /api/v1.', + 'settings.apiKeys.modal.createTitle': 'Crear clave API', + 'settings.apiKeys.modal.name': 'Nombre', + 'settings.apiKeys.modal.namePlaceholder': 'p. ej. Dawarich', + 'settings.apiKeys.modal.nameHint': 'Solo para ti, para que reconozcas la clave más adelante.', + 'settings.apiKeys.modal.creating': 'Creando...', + 'settings.apiKeys.modal.create': 'Crear', + 'settings.apiKeys.modal.createdTitle': 'Clave API creada', + 'settings.apiKeys.modal.createdWarning': 'Copia la clave ahora. Se muestra una sola vez y no se puede recuperar después.', + 'settings.apiKeys.modal.done': 'Listo', }; export default settings; diff --git a/shared/src/i18n/fr/settings.ts b/shared/src/i18n/fr/settings.ts index 78895bdb9..c31dda7cc 100644 --- a/shared/src/i18n/fr/settings.ts +++ b/shared/src/i18n/fr/settings.ts @@ -541,6 +541,30 @@ const settings: TranslationStrings = { 'settings.alwaysShowRoutes': 'Toujours afficher les itinéraires de réservation', 'settings.alwaysShowRoutesHint': "Affiche automatiquement sur la carte l'itinéraire de chaque vol, trajet en train et autre réservation, sans avoir à l'activer un par un.", + + // Public API keys (Settings -> Integrations) + 'settings.apiKeys.title': 'Clés API', + 'settings.apiKeys.description': 'Clés pour l\'API publique, afin que d\'autres logiciels puissent lire vos voyages. Lecture seule : une clé ne peut rien modifier ni supprimer.', + 'settings.apiKeys.create': 'Créer une clé', + 'settings.apiKeys.empty': 'Aucune clé pour le moment. Créez-en une pour connecter un autre logiciel.', + 'settings.apiKeys.createdAt': 'créée', + 'settings.apiKeys.usedAt': 'dernière utilisation', + 'settings.apiKeys.deleteTitle': 'Supprimer la clé', + 'settings.apiKeys.deleteMessage': 'Tout ce qui utilise cette clé cesse de fonctionner immédiatement. Cette action est irréversible.', + 'settings.apiKeys.deleted': 'Clé supprimée', + 'settings.apiKeys.deleteFailed': 'Impossible de supprimer la clé', + 'settings.apiKeys.createFailed': 'Impossible de créer la clé', + 'settings.apiKeys.copy': 'Copier', + 'settings.apiKeys.docsHint': 'Envoyez la clé via "Authorization: Bearer ..." ou "X-API-Key: ..." vers /api/v1.', + 'settings.apiKeys.modal.createTitle': 'Créer une clé API', + 'settings.apiKeys.modal.name': 'Nom', + 'settings.apiKeys.modal.namePlaceholder': 'p. ex. Dawarich', + 'settings.apiKeys.modal.nameHint': 'Uniquement pour vous, afin de reconnaître la clé plus tard.', + 'settings.apiKeys.modal.creating': 'Création...', + 'settings.apiKeys.modal.create': 'Créer', + 'settings.apiKeys.modal.createdTitle': 'Clé API créée', + 'settings.apiKeys.modal.createdWarning': 'Copiez la clé maintenant. Elle n\'est affichée qu\'une seule fois et ne peut pas être récupérée ensuite.', + 'settings.apiKeys.modal.done': 'Terminé', }; export default settings; diff --git a/shared/src/i18n/gr/settings.ts b/shared/src/i18n/gr/settings.ts index 35454b4fe..aed5bbb5a 100644 --- a/shared/src/i18n/gr/settings.ts +++ b/shared/src/i18n/gr/settings.ts @@ -544,6 +544,30 @@ const settings: TranslationStrings = { 'settings.alwaysShowRoutes': 'Πάντα εμφάνιση διαδρομών κράτησης', 'settings.alwaysShowRoutesHint': 'Εμφανίζει αυτόματα στον χάρτη τη διαδρομή κάθε πτήσης, τρένου και άλλης κράτησης, χωρίς να χρειάζεται να την ενεργοποιείτε μία προς μία.', + + // Public API keys (Settings -> Integrations) + 'settings.apiKeys.title': 'Κλειδιά API', + 'settings.apiKeys.description': 'Κλειδιά για το δημόσιο API, ώστε άλλο λογισμικό να μπορεί να διαβάζει τα ταξίδια σου. Μόνο για ανάγνωση: ένα κλειδί δεν μπορεί να αλλάξει ή να διαγράψει τίποτα.', + 'settings.apiKeys.create': 'Δημιουργία κλειδιού', + 'settings.apiKeys.empty': 'Δεν υπάρχουν κλειδιά ακόμη. Δημιούργησε ένα για να συνδέσεις άλλο λογισμικό.', + 'settings.apiKeys.createdAt': 'δημιουργήθηκε', + 'settings.apiKeys.usedAt': 'τελευταία χρήση', + 'settings.apiKeys.deleteTitle': 'Διαγραφή κλειδιού', + 'settings.apiKeys.deleteMessage': 'Ό,τι χρησιμοποιεί αυτό το κλειδί σταματά αμέσως να λειτουργεί. Η ενέργεια δεν αναιρείται.', + 'settings.apiKeys.deleted': 'Το κλειδί διαγράφηκε', + 'settings.apiKeys.deleteFailed': 'Δεν ήταν δυνατή η διαγραφή του κλειδιού', + 'settings.apiKeys.createFailed': 'Δεν ήταν δυνατή η δημιουργία του κλειδιού', + 'settings.apiKeys.copy': 'Αντιγραφή', + 'settings.apiKeys.docsHint': 'Στείλε το κλειδί ως "Authorization: Bearer ..." ή "X-API-Key: ..." στο /api/v1.', + 'settings.apiKeys.modal.createTitle': 'Δημιουργία κλειδιού API', + 'settings.apiKeys.modal.name': 'Όνομα', + 'settings.apiKeys.modal.namePlaceholder': 'π.χ. Dawarich', + 'settings.apiKeys.modal.nameHint': 'Μόνο για σένα, για να αναγνωρίζεις το κλειδί αργότερα.', + 'settings.apiKeys.modal.creating': 'Δημιουργία...', + 'settings.apiKeys.modal.create': 'Δημιουργία', + 'settings.apiKeys.modal.createdTitle': 'Το κλειδί API δημιουργήθηκε', + 'settings.apiKeys.modal.createdWarning': 'Αντίγραψε το κλειδί τώρα. Εμφανίζεται μία φορά και δεν μπορεί να ανακτηθεί αργότερα.', + 'settings.apiKeys.modal.done': 'Έτοιμο', }; export default settings; diff --git a/shared/src/i18n/hu/settings.ts b/shared/src/i18n/hu/settings.ts index 02ff6a04c..c25efa6e8 100644 --- a/shared/src/i18n/hu/settings.ts +++ b/shared/src/i18n/hu/settings.ts @@ -534,6 +534,30 @@ const settings: TranslationStrings = { 'settings.alwaysShowRoutes': 'Mindig jelenjenek meg a foglalási útvonalak', 'settings.alwaysShowRoutesHint': 'Automatikusan megjeleníti minden repülőjárat, vonat és egyéb foglalás útvonalát a térképen, nincs szükség egyenkénti bekapcsolásra.', + + // Public API keys (Settings -> Integrations) + 'settings.apiKeys.title': 'API-kulcsok', + 'settings.apiKeys.description': 'Kulcsok a nyilvános API-hoz, hogy más szoftverek olvashassák az utazásaidat. Csak olvasható: a kulcs semmit nem módosít és nem töröl.', + 'settings.apiKeys.create': 'Kulcs létrehozása', + 'settings.apiKeys.empty': 'Még nincs kulcs. Hozz létre egyet más szoftver csatlakoztatásához.', + 'settings.apiKeys.createdAt': 'létrehozva', + 'settings.apiKeys.usedAt': 'utoljára használva', + 'settings.apiKeys.deleteTitle': 'Kulcs törlése', + 'settings.apiKeys.deleteMessage': 'Minden, ami ezt a kulcsot használja, azonnal leáll. A művelet nem vonható vissza.', + 'settings.apiKeys.deleted': 'Kulcs törölve', + 'settings.apiKeys.deleteFailed': 'A kulcsot nem sikerült törölni', + 'settings.apiKeys.createFailed': 'A kulcsot nem sikerült létrehozni', + 'settings.apiKeys.copy': 'Másolás', + 'settings.apiKeys.docsHint': 'Küldd a kulcsot "Authorization: Bearer ..." vagy "X-API-Key: ..." fejlécként a /api/v1 címre.', + 'settings.apiKeys.modal.createTitle': 'API-kulcs létrehozása', + 'settings.apiKeys.modal.name': 'Név', + 'settings.apiKeys.modal.namePlaceholder': 'pl. Dawarich', + 'settings.apiKeys.modal.nameHint': 'Csak neked, hogy később felismerd a kulcsot.', + 'settings.apiKeys.modal.creating': 'Létrehozás folyamatban...', + 'settings.apiKeys.modal.create': 'Létrehozás', + 'settings.apiKeys.modal.createdTitle': 'API-kulcs létrehozva', + 'settings.apiKeys.modal.createdWarning': 'Másold ki a kulcsot most. Csak egyszer jelenik meg, később nem kérhető le.', + 'settings.apiKeys.modal.done': 'Kész', }; export default settings; diff --git a/shared/src/i18n/id/settings.ts b/shared/src/i18n/id/settings.ts index a8d4f40e7..0defc1f36 100644 --- a/shared/src/i18n/id/settings.ts +++ b/shared/src/i18n/id/settings.ts @@ -530,6 +530,30 @@ const settings: TranslationStrings = { 'settings.alwaysShowRoutes': 'Selalu tampilkan rute pemesanan', 'settings.alwaysShowRoutesHint': 'Menampilkan rute setiap penerbangan, kereta, dan pemesanan lainnya di peta secara otomatis, tanpa perlu mengaktifkannya satu per satu.', + + // Public API keys (Settings -> Integrations) + 'settings.apiKeys.title': 'Kunci API', + 'settings.apiKeys.description': 'Kunci untuk API publik, agar perangkat lunak lain dapat membaca perjalananmu. Hanya baca: kunci tidak dapat mengubah atau menghapus apa pun.', + 'settings.apiKeys.create': 'Buat kunci', + 'settings.apiKeys.empty': 'Belum ada kunci. Buat satu untuk menghubungkan perangkat lunak lain.', + 'settings.apiKeys.createdAt': 'dibuat', + 'settings.apiKeys.usedAt': 'terakhir dipakai', + 'settings.apiKeys.deleteTitle': 'Hapus kunci', + 'settings.apiKeys.deleteMessage': 'Semua yang memakai kunci ini langsung berhenti bekerja. Tindakan ini tidak bisa dibatalkan.', + 'settings.apiKeys.deleted': 'Kunci dihapus', + 'settings.apiKeys.deleteFailed': 'Kunci tidak dapat dihapus', + 'settings.apiKeys.createFailed': 'Kunci tidak dapat dibuat', + 'settings.apiKeys.copy': 'Salin', + 'settings.apiKeys.docsHint': 'Kirim kunci sebagai "Authorization: Bearer ..." atau "X-API-Key: ..." ke /api/v1.', + 'settings.apiKeys.modal.createTitle': 'Buat kunci API', + 'settings.apiKeys.modal.name': 'Nama', + 'settings.apiKeys.modal.namePlaceholder': 'mis. Dawarich', + 'settings.apiKeys.modal.nameHint': 'Hanya untukmu, agar kamu mengenali kunci ini nanti.', + 'settings.apiKeys.modal.creating': 'Membuat...', + 'settings.apiKeys.modal.create': 'Buat', + 'settings.apiKeys.modal.createdTitle': 'Kunci API dibuat', + 'settings.apiKeys.modal.createdWarning': 'Salin kunci sekarang. Kunci hanya ditampilkan sekali dan tidak bisa diambil lagi.', + 'settings.apiKeys.modal.done': 'Selesai', }; export default settings; diff --git a/shared/src/i18n/it/settings.ts b/shared/src/i18n/it/settings.ts index b79296c4b..d59c6ece8 100644 --- a/shared/src/i18n/it/settings.ts +++ b/shared/src/i18n/it/settings.ts @@ -532,6 +532,30 @@ const settings: TranslationStrings = { 'settings.alwaysShowRoutes': 'Mostra sempre i percorsi delle prenotazioni', 'settings.alwaysShowRoutesHint': 'Mostra automaticamente sulla mappa il percorso di ogni volo, treno e altra prenotazione, senza doverlo attivare singolarmente.', + + // Public API keys (Settings -> Integrations) + 'settings.apiKeys.title': 'Chiavi API', + 'settings.apiKeys.description': 'Chiavi per l\'API pubblica, così altri software possono leggere i tuoi viaggi. Sola lettura: una chiave non può modificare né eliminare nulla.', + 'settings.apiKeys.create': 'Crea chiave', + 'settings.apiKeys.empty': 'Nessuna chiave. Creane una per collegare altri software.', + 'settings.apiKeys.createdAt': 'creata', + 'settings.apiKeys.usedAt': 'ultimo uso', + 'settings.apiKeys.deleteTitle': 'Elimina chiave', + 'settings.apiKeys.deleteMessage': 'Tutto ciò che usa questa chiave smette di funzionare subito. L\'operazione non è reversibile.', + 'settings.apiKeys.deleted': 'Chiave eliminata', + 'settings.apiKeys.deleteFailed': 'Impossibile eliminare la chiave', + 'settings.apiKeys.createFailed': 'Impossibile creare la chiave', + 'settings.apiKeys.copy': 'Copia', + 'settings.apiKeys.docsHint': 'Invia la chiave come "Authorization: Bearer ..." oppure "X-API-Key: ..." a /api/v1.', + 'settings.apiKeys.modal.createTitle': 'Crea chiave API', + 'settings.apiKeys.modal.name': 'Nome', + 'settings.apiKeys.modal.namePlaceholder': 'ad es. Dawarich', + 'settings.apiKeys.modal.nameHint': 'Solo per te, per riconoscere la chiave in seguito.', + 'settings.apiKeys.modal.creating': 'Creazione...', + 'settings.apiKeys.modal.create': 'Crea', + 'settings.apiKeys.modal.createdTitle': 'Chiave API creata', + 'settings.apiKeys.modal.createdWarning': 'Copia la chiave adesso. Viene mostrata una sola volta e non può essere recuperata in seguito.', + 'settings.apiKeys.modal.done': 'Fatto', }; export default settings; diff --git a/shared/src/i18n/ja/settings.ts b/shared/src/i18n/ja/settings.ts index f7cc4ca5e..cb85e6208 100644 --- a/shared/src/i18n/ja/settings.ts +++ b/shared/src/i18n/ja/settings.ts @@ -502,6 +502,30 @@ const settings: TranslationStrings = { 'settings.alwaysShowRoutes': '予約ルートを常に表示', 'settings.alwaysShowRoutesHint': 'フライトや電車などすべての予約のルートを、個別にオンにしなくても地図上に自動的に表示します。', + + // Public API keys (Settings -> Integrations) + 'settings.apiKeys.title': 'API キー', + 'settings.apiKeys.description': '公開 API 用のキーです。ほかのソフトウェアが旅程を読み取れるようになります。読み取り専用で、変更や削除はできません。', + 'settings.apiKeys.create': 'キーを作成', + 'settings.apiKeys.empty': 'キーはまだありません。ほかのソフトウェアと連携するには作成してください。', + 'settings.apiKeys.createdAt': '作成日', + 'settings.apiKeys.usedAt': '最終使用', + 'settings.apiKeys.deleteTitle': 'キーを削除', + 'settings.apiKeys.deleteMessage': 'このキーを使っているものはすぐに動かなくなります。取り消しはできません。', + 'settings.apiKeys.deleted': 'キーを削除しました', + 'settings.apiKeys.deleteFailed': 'キーを削除できませんでした', + 'settings.apiKeys.createFailed': 'キーを作成できませんでした', + 'settings.apiKeys.copy': 'コピー', + 'settings.apiKeys.docsHint': 'キーは "Authorization: Bearer ..." または "X-API-Key: ..." として /api/v1 に送信してください。', + 'settings.apiKeys.modal.createTitle': 'API キーを作成', + 'settings.apiKeys.modal.name': '名前', + 'settings.apiKeys.modal.namePlaceholder': '例: Dawarich', + 'settings.apiKeys.modal.nameHint': 'あとで見分けるための、あなた用の名前です。', + 'settings.apiKeys.modal.creating': '作成中...', + 'settings.apiKeys.modal.create': '作成', + 'settings.apiKeys.modal.createdTitle': 'API キーを作成しました', + 'settings.apiKeys.modal.createdWarning': '今すぐキーをコピーしてください。表示は一度きりで、あとから取得はできません。', + 'settings.apiKeys.modal.done': '完了', }; export default settings; diff --git a/shared/src/i18n/ko/settings.ts b/shared/src/i18n/ko/settings.ts index 013f1098d..1a1d518bd 100644 --- a/shared/src/i18n/ko/settings.ts +++ b/shared/src/i18n/ko/settings.ts @@ -519,6 +519,30 @@ const settings: TranslationStrings = { 'settings.alwaysShowRoutes': '예약 경로 항상 표시', 'settings.alwaysShowRoutesHint': '항공편, 기차 등 모든 예약의 경로를 개별적으로 켤 필요 없이 지도에 자동으로 표시합니다.', + + // Public API keys (Settings -> Integrations) + 'settings.apiKeys.title': 'API 키', + 'settings.apiKeys.description': '공개 API용 키입니다. 다른 소프트웨어가 여행을 읽을 수 있습니다. 읽기 전용이라 무엇도 바꾸거나 지울 수 없습니다.', + 'settings.apiKeys.create': '키 만들기', + 'settings.apiKeys.empty': '아직 키가 없습니다. 다른 소프트웨어를 연결하려면 하나 만드세요.', + 'settings.apiKeys.createdAt': '생성일', + 'settings.apiKeys.usedAt': '마지막 사용', + 'settings.apiKeys.deleteTitle': '키 삭제', + 'settings.apiKeys.deleteMessage': '이 키를 쓰는 모든 것이 즉시 작동을 멈춥니다. 되돌릴 수 없습니다.', + 'settings.apiKeys.deleted': '키를 삭제했습니다', + 'settings.apiKeys.deleteFailed': '키를 삭제하지 못했습니다', + 'settings.apiKeys.createFailed': '키를 만들지 못했습니다', + 'settings.apiKeys.copy': '복사', + 'settings.apiKeys.docsHint': '키를 "Authorization: Bearer ..." 또는 "X-API-Key: ..." 로 /api/v1 에 보내세요.', + 'settings.apiKeys.modal.createTitle': 'API 키 만들기', + 'settings.apiKeys.modal.name': '이름', + 'settings.apiKeys.modal.namePlaceholder': '예: Dawarich', + 'settings.apiKeys.modal.nameHint': '나중에 키를 알아보기 위한, 본인용 이름입니다.', + 'settings.apiKeys.modal.creating': '만드는 중...', + 'settings.apiKeys.modal.create': '만들기', + 'settings.apiKeys.modal.createdTitle': 'API 키를 만들었습니다', + 'settings.apiKeys.modal.createdWarning': '지금 키를 복사하세요. 한 번만 표시되며 나중에 다시 확인할 수 없습니다.', + 'settings.apiKeys.modal.done': '완료', }; export default settings; diff --git a/shared/src/i18n/nl/settings.ts b/shared/src/i18n/nl/settings.ts index 0f26c2f30..25fd4fd93 100644 --- a/shared/src/i18n/nl/settings.ts +++ b/shared/src/i18n/nl/settings.ts @@ -533,6 +533,30 @@ const settings: TranslationStrings = { 'settings.alwaysShowRoutes': 'Boekingsroutes altijd tonen', 'settings.alwaysShowRoutesHint': 'Toont automatisch de route van elke vlucht, trein en andere boeking op de kaart, zonder dat u dit per boeking hoeft in te schakelen.', + + // Public API keys (Settings -> Integrations) + 'settings.apiKeys.title': 'API-sleutels', + 'settings.apiKeys.description': 'Sleutels voor de publieke API, zodat andere software je reizen kan lezen. Alleen-lezen: een sleutel kan niets wijzigen of verwijderen.', + 'settings.apiKeys.create': 'Sleutel aanmaken', + 'settings.apiKeys.empty': 'Nog geen sleutels. Maak er een om andere software te koppelen.', + 'settings.apiKeys.createdAt': 'aangemaakt', + 'settings.apiKeys.usedAt': 'laatst gebruikt', + 'settings.apiKeys.deleteTitle': 'Sleutel verwijderen', + 'settings.apiKeys.deleteMessage': 'Alles wat deze sleutel gebruikt, stopt direct met werken. Dit kan niet ongedaan worden gemaakt.', + 'settings.apiKeys.deleted': 'Sleutel verwijderd', + 'settings.apiKeys.deleteFailed': 'Sleutel kon niet worden verwijderd', + 'settings.apiKeys.createFailed': 'Sleutel kon niet worden aangemaakt', + 'settings.apiKeys.copy': 'Kopiëren', + 'settings.apiKeys.docsHint': 'Stuur de sleutel als "Authorization: Bearer ..." of "X-API-Key: ..." naar /api/v1.', + 'settings.apiKeys.modal.createTitle': 'API-sleutel aanmaken', + 'settings.apiKeys.modal.name': 'Naam', + 'settings.apiKeys.modal.namePlaceholder': 'bijv. Dawarich', + 'settings.apiKeys.modal.nameHint': 'Alleen voor jou, zodat je de sleutel later herkent.', + 'settings.apiKeys.modal.creating': 'Aanmaken...', + 'settings.apiKeys.modal.create': 'Aanmaken', + 'settings.apiKeys.modal.createdTitle': 'API-sleutel aangemaakt', + 'settings.apiKeys.modal.createdWarning': 'Kopieer de sleutel nu. Hij wordt één keer getoond en kan later niet worden opgehaald.', + 'settings.apiKeys.modal.done': 'Klaar', }; export default settings; diff --git a/shared/src/i18n/pl/settings.ts b/shared/src/i18n/pl/settings.ts index 498142f87..f09206db1 100644 --- a/shared/src/i18n/pl/settings.ts +++ b/shared/src/i18n/pl/settings.ts @@ -532,6 +532,30 @@ const settings: TranslationStrings = { 'settings.alwaysShowRoutes': 'Zawsze pokazuj trasy rezerwacji', 'settings.alwaysShowRoutesHint': 'Automatycznie pokazuje na mapie trasę każdego lotu, pociągu i innej rezerwacji, bez konieczności włączania jej pojedynczo.', + + // Public API keys (Settings -> Integrations) + 'settings.apiKeys.title': 'Klucze API', + 'settings.apiKeys.description': 'Klucze do publicznego API, aby inne oprogramowanie mogło odczytywać Twoje podróże. Tylko odczyt: klucz niczego nie zmieni ani nie usunie.', + 'settings.apiKeys.create': 'Utwórz klucz', + 'settings.apiKeys.empty': 'Brak kluczy. Utwórz jeden, aby połączyć inne oprogramowanie.', + 'settings.apiKeys.createdAt': 'utworzono', + 'settings.apiKeys.usedAt': 'ostatnio użyty', + 'settings.apiKeys.deleteTitle': 'Usuń klucz', + 'settings.apiKeys.deleteMessage': 'Wszystko, co korzysta z tego klucza, natychmiast przestanie działać. Tej operacji nie można cofnąć.', + 'settings.apiKeys.deleted': 'Klucz usunięty', + 'settings.apiKeys.deleteFailed': 'Nie udało się usunąć klucza', + 'settings.apiKeys.createFailed': 'Nie udało się utworzyć klucza', + 'settings.apiKeys.copy': 'Kopiuj', + 'settings.apiKeys.docsHint': 'Wyślij klucz jako "Authorization: Bearer ..." lub "X-API-Key: ..." do /api/v1.', + 'settings.apiKeys.modal.createTitle': 'Utwórz klucz API', + 'settings.apiKeys.modal.name': 'Nazwa', + 'settings.apiKeys.modal.namePlaceholder': 'np. Dawarich', + 'settings.apiKeys.modal.nameHint': 'Tylko dla Ciebie, abyś później rozpoznał klucz.', + 'settings.apiKeys.modal.creating': 'Tworzenie...', + 'settings.apiKeys.modal.create': 'Utwórz', + 'settings.apiKeys.modal.createdTitle': 'Klucz API utworzony', + 'settings.apiKeys.modal.createdWarning': 'Skopiuj klucz teraz. Jest pokazywany tylko raz i później nie można go odzyskać.', + 'settings.apiKeys.modal.done': 'Gotowe', }; export default settings; diff --git a/shared/src/i18n/ru/settings.ts b/shared/src/i18n/ru/settings.ts index acb6bd029..69ebad980 100644 --- a/shared/src/i18n/ru/settings.ts +++ b/shared/src/i18n/ru/settings.ts @@ -534,6 +534,30 @@ const settings: TranslationStrings = { 'settings.alwaysShowRoutes': 'Всегда показывать маршруты бронирований', 'settings.alwaysShowRoutesHint': 'Автоматически показывает на карте маршрут для каждого рейса, поезда и другого бронирования — без необходимости включать это отдельно для каждого элемента.', + + // Public API keys (Settings -> Integrations) + 'settings.apiKeys.title': 'Ключи API', + 'settings.apiKeys.description': 'Ключи для публичного API, чтобы другие программы могли читать ваши поездки. Только чтение: ключ ничего не изменит и не удалит.', + 'settings.apiKeys.create': 'Создать ключ', + 'settings.apiKeys.empty': 'Ключей пока нет. Создайте один, чтобы подключить другую программу.', + 'settings.apiKeys.createdAt': 'создан', + 'settings.apiKeys.usedAt': 'последнее использование', + 'settings.apiKeys.deleteTitle': 'Удалить ключ', + 'settings.apiKeys.deleteMessage': 'Всё, что использует этот ключ, сразу перестанет работать. Отменить нельзя.', + 'settings.apiKeys.deleted': 'Ключ удалён', + 'settings.apiKeys.deleteFailed': 'Не удалось удалить ключ', + 'settings.apiKeys.createFailed': 'Не удалось создать ключ', + 'settings.apiKeys.copy': 'Копировать', + 'settings.apiKeys.docsHint': 'Отправляйте ключ как "Authorization: Bearer ..." или "X-API-Key: ..." на /api/v1.', + 'settings.apiKeys.modal.createTitle': 'Создать ключ API', + 'settings.apiKeys.modal.name': 'Название', + 'settings.apiKeys.modal.namePlaceholder': 'например, Dawarich', + 'settings.apiKeys.modal.nameHint': 'Только для вас, чтобы позже узнать ключ.', + 'settings.apiKeys.modal.creating': 'Создание...', + 'settings.apiKeys.modal.create': 'Создать', + 'settings.apiKeys.modal.createdTitle': 'Ключ API создан', + 'settings.apiKeys.modal.createdWarning': 'Скопируйте ключ сейчас. Он показывается один раз, позже получить его нельзя.', + 'settings.apiKeys.modal.done': 'Готово', }; export default settings; diff --git a/shared/src/i18n/sv/settings.ts b/shared/src/i18n/sv/settings.ts index 2d5efa1ec..80e0fdc95 100644 --- a/shared/src/i18n/sv/settings.ts +++ b/shared/src/i18n/sv/settings.ts @@ -529,6 +529,30 @@ const settings: TranslationStrings = { 'settings.alwaysShowRoutes': 'Visa alltid bokningsrutter', 'settings.alwaysShowRoutesHint': 'Visar automatiskt rutten för varje flyg, tåg och annan bokning på kartan, utan att du behöver aktivera det för varje bokning för sig.', + + // Public API keys (Settings -> Integrations) + 'settings.apiKeys.title': 'API-nycklar', + 'settings.apiKeys.description': 'Nycklar för det publika API:et, så att annan programvara kan läsa dina resor. Endast läsning: en nyckel kan inte ändra eller ta bort något.', + 'settings.apiKeys.create': 'Skapa nyckel', + 'settings.apiKeys.empty': 'Inga nycklar än. Skapa en för att koppla annan programvara.', + 'settings.apiKeys.createdAt': 'skapad', + 'settings.apiKeys.usedAt': 'senast använd', + 'settings.apiKeys.deleteTitle': 'Ta bort nyckel', + 'settings.apiKeys.deleteMessage': 'Allt som använder den här nyckeln slutar fungera direkt. Det går inte att ångra.', + 'settings.apiKeys.deleted': 'Nyckeln togs bort', + 'settings.apiKeys.deleteFailed': 'Kunde inte ta bort nyckeln', + 'settings.apiKeys.createFailed': 'Kunde inte skapa nyckeln', + 'settings.apiKeys.copy': 'Kopiera', + 'settings.apiKeys.docsHint': 'Skicka nyckeln som "Authorization: Bearer ..." eller "X-API-Key: ..." till /api/v1.', + 'settings.apiKeys.modal.createTitle': 'Skapa API-nyckel', + 'settings.apiKeys.modal.name': 'Namn', + 'settings.apiKeys.modal.namePlaceholder': 't.ex. Dawarich', + 'settings.apiKeys.modal.nameHint': 'Bara för dig, så att du känner igen nyckeln senare.', + 'settings.apiKeys.modal.creating': 'Skapar...', + 'settings.apiKeys.modal.create': 'Skapa', + 'settings.apiKeys.modal.createdTitle': 'API-nyckel skapad', + 'settings.apiKeys.modal.createdWarning': 'Kopiera nyckeln nu. Den visas en gång och kan inte hämtas senare.', + 'settings.apiKeys.modal.done': 'Klar', }; export default settings; diff --git a/shared/src/i18n/tr/settings.ts b/shared/src/i18n/tr/settings.ts index 9afe7f038..e68cb8a36 100644 --- a/shared/src/i18n/tr/settings.ts +++ b/shared/src/i18n/tr/settings.ts @@ -530,6 +530,30 @@ const settings: TranslationStrings = { 'settings.alwaysShowRoutes': 'Rezervasyon rotalarını her zaman göster', 'settings.alwaysShowRoutesHint': 'Haritada her uçuş, tren ve diğer rezervasyonun rotasını, tek tek açmaya gerek kalmadan otomatik olarak gösterir.', + + // Public API keys (Settings -> Integrations) + 'settings.apiKeys.title': 'API Anahtarları', + 'settings.apiKeys.description': 'Genel API için anahtarlar, böylece başka yazılımlar gezilerini okuyabilir. Yalnızca okuma: bir anahtar hiçbir şeyi değiştiremez veya silemez.', + 'settings.apiKeys.create': 'Anahtar oluştur', + 'settings.apiKeys.empty': 'Henüz anahtar yok. Başka bir yazılımı bağlamak için bir tane oluştur.', + 'settings.apiKeys.createdAt': 'oluşturuldu', + 'settings.apiKeys.usedAt': 'son kullanım', + 'settings.apiKeys.deleteTitle': 'Anahtarı sil', + 'settings.apiKeys.deleteMessage': 'Bu anahtarı kullanan her şey hemen çalışmayı durdurur. Bu işlem geri alınamaz.', + 'settings.apiKeys.deleted': 'Anahtar silindi', + 'settings.apiKeys.deleteFailed': 'Anahtar silinemedi', + 'settings.apiKeys.createFailed': 'Anahtar oluşturulamadı', + 'settings.apiKeys.copy': 'Kopyala', + 'settings.apiKeys.docsHint': 'Anahtarı "Authorization: Bearer ..." veya "X-API-Key: ..." olarak /api/v1 adresine gönder.', + 'settings.apiKeys.modal.createTitle': 'API anahtarı oluştur', + 'settings.apiKeys.modal.name': 'Ad', + 'settings.apiKeys.modal.namePlaceholder': 'örn. Dawarich', + 'settings.apiKeys.modal.nameHint': 'Yalnızca senin için, anahtarı sonradan tanıyabilmen için.', + 'settings.apiKeys.modal.creating': 'Oluşturuluyor...', + 'settings.apiKeys.modal.create': 'Oluştur', + 'settings.apiKeys.modal.createdTitle': 'API anahtarı oluşturuldu', + 'settings.apiKeys.modal.createdWarning': 'Anahtarı şimdi kopyala. Yalnızca bir kez gösterilir ve sonradan alınamaz.', + 'settings.apiKeys.modal.done': 'Tamam', }; export default settings; diff --git a/shared/src/i18n/uk/settings.ts b/shared/src/i18n/uk/settings.ts index 9856aa90d..cc245bb67 100644 --- a/shared/src/i18n/uk/settings.ts +++ b/shared/src/i18n/uk/settings.ts @@ -533,6 +533,30 @@ const settings: TranslationStrings = { 'settings.alwaysShowRoutes': 'Завжди показувати маршрути бронювань', 'settings.alwaysShowRoutesHint': 'Автоматично показує на карті маршрут для кожного рейсу, поїзда та іншого бронювання — без потреби вмикати це окремо для кожного елемента.', + + // Public API keys (Settings -> Integrations) + 'settings.apiKeys.title': 'Ключі API', + 'settings.apiKeys.description': 'Ключі для публічного API, щоб інші програми могли читати ваші подорожі. Лише читання: ключ нічого не змінить і не видалить.', + 'settings.apiKeys.create': 'Створити ключ', + 'settings.apiKeys.empty': 'Ключів ще немає. Створіть один, щоб підключити іншу програму.', + 'settings.apiKeys.createdAt': 'створено', + 'settings.apiKeys.usedAt': 'востаннє використано', + 'settings.apiKeys.deleteTitle': 'Видалити ключ', + 'settings.apiKeys.deleteMessage': 'Усе, що використовує цей ключ, одразу перестане працювати. Скасувати не можна.', + 'settings.apiKeys.deleted': 'Ключ видалено', + 'settings.apiKeys.deleteFailed': 'Не вдалося видалити ключ', + 'settings.apiKeys.createFailed': 'Не вдалося створити ключ', + 'settings.apiKeys.copy': 'Копіювати', + 'settings.apiKeys.docsHint': 'Надсилайте ключ як "Authorization: Bearer ..." або "X-API-Key: ..." на /api/v1.', + 'settings.apiKeys.modal.createTitle': 'Створити ключ API', + 'settings.apiKeys.modal.name': 'Назва', + 'settings.apiKeys.modal.namePlaceholder': 'напр. Dawarich', + 'settings.apiKeys.modal.nameHint': 'Лише для вас, щоб пізніше впізнати ключ.', + 'settings.apiKeys.modal.creating': 'Створення...', + 'settings.apiKeys.modal.create': 'Створити', + 'settings.apiKeys.modal.createdTitle': 'Ключ API створено', + 'settings.apiKeys.modal.createdWarning': 'Скопіюйте ключ зараз. Він показується один раз, пізніше його не отримати.', + 'settings.apiKeys.modal.done': 'Готово', }; export default settings; diff --git a/shared/src/i18n/vi/settings.ts b/shared/src/i18n/vi/settings.ts index d401e398a..948c48a43 100644 --- a/shared/src/i18n/vi/settings.ts +++ b/shared/src/i18n/vi/settings.ts @@ -534,6 +534,30 @@ const settings: TranslationStrings = { 'settings.alwaysShowRoutes': 'Luôn hiển thị tuyến đường đặt chỗ', 'settings.alwaysShowRoutesHint': 'Tự động hiển thị trên bản đồ tuyến đường của mỗi chuyến bay, tàu hỏa và đặt chỗ khác, không cần bật riêng từng mục.', + + // Public API keys (Settings -> Integrations) + 'settings.apiKeys.title': 'Khóa API', + 'settings.apiKeys.description': 'Khóa cho API công khai, để phần mềm khác có thể đọc các chuyến đi của bạn. Chỉ đọc: khóa không thể thay đổi hay xóa bất cứ thứ gì.', + 'settings.apiKeys.create': 'Tạo khóa', + 'settings.apiKeys.empty': 'Chưa có khóa nào. Tạo một khóa để kết nối phần mềm khác.', + 'settings.apiKeys.createdAt': 'đã tạo', + 'settings.apiKeys.usedAt': 'dùng lần cuối', + 'settings.apiKeys.deleteTitle': 'Xóa khóa', + 'settings.apiKeys.deleteMessage': 'Mọi thứ đang dùng khóa này sẽ ngừng hoạt động ngay lập tức. Không thể hoàn tác.', + 'settings.apiKeys.deleted': 'Đã xóa khóa', + 'settings.apiKeys.deleteFailed': 'Không thể xóa khóa', + 'settings.apiKeys.createFailed': 'Không thể tạo khóa', + 'settings.apiKeys.copy': 'Sao chép', + 'settings.apiKeys.docsHint': 'Gửi khóa dưới dạng "Authorization: Bearer ..." hoặc "X-API-Key: ..." tới /api/v1.', + 'settings.apiKeys.modal.createTitle': 'Tạo khóa API', + 'settings.apiKeys.modal.name': 'Tên', + 'settings.apiKeys.modal.namePlaceholder': 'ví dụ Dawarich', + 'settings.apiKeys.modal.nameHint': 'Chỉ dành cho bạn, để nhận ra khóa này về sau.', + 'settings.apiKeys.modal.creating': 'Đang tạo...', + 'settings.apiKeys.modal.create': 'Tạo', + 'settings.apiKeys.modal.createdTitle': 'Đã tạo khóa API', + 'settings.apiKeys.modal.createdWarning': 'Hãy sao chép khóa ngay. Khóa chỉ hiện một lần và không thể lấy lại sau này.', + 'settings.apiKeys.modal.done': 'Xong', }; export default settings; diff --git a/shared/src/i18n/zh-TW/settings.ts b/shared/src/i18n/zh-TW/settings.ts index 966954eb6..9d62e68ec 100644 --- a/shared/src/i18n/zh-TW/settings.ts +++ b/shared/src/i18n/zh-TW/settings.ts @@ -496,6 +496,30 @@ const settings: TranslationStrings = { 'settings.pluginActivity.columns.status': '結果', 'settings.alwaysShowRoutes': '一律顯示訂票路線', 'settings.alwaysShowRoutesHint': '自動在地圖上顯示每個航班、火車及其他預訂的路線,不需要逐一手動開啟。', + + // Public API keys (Settings -> Integrations) + 'settings.apiKeys.title': 'API 金鑰', + 'settings.apiKeys.description': '公開 API 的金鑰,讓其他軟體讀取你的行程。唯讀:金鑰無法修改或刪除任何內容。', + 'settings.apiKeys.create': '建立金鑰', + 'settings.apiKeys.empty': '尚無金鑰。建立一組即可連接其他軟體。', + 'settings.apiKeys.createdAt': '建立於', + 'settings.apiKeys.usedAt': '最近使用', + 'settings.apiKeys.deleteTitle': '刪除金鑰', + 'settings.apiKeys.deleteMessage': '使用這組金鑰的一切都會立即停止運作,且無法復原。', + 'settings.apiKeys.deleted': '金鑰已刪除', + 'settings.apiKeys.deleteFailed': '無法刪除金鑰', + 'settings.apiKeys.createFailed': '無法建立金鑰', + 'settings.apiKeys.copy': '複製', + 'settings.apiKeys.docsHint': '將金鑰以 "Authorization: Bearer ..." 或 "X-API-Key: ..." 傳送到 /api/v1。', + 'settings.apiKeys.modal.createTitle': '建立 API 金鑰', + 'settings.apiKeys.modal.name': '名稱', + 'settings.apiKeys.modal.namePlaceholder': '例如 Dawarich', + 'settings.apiKeys.modal.nameHint': '僅供你自己辨識,方便日後認出這組金鑰。', + 'settings.apiKeys.modal.creating': '建立中...', + 'settings.apiKeys.modal.create': '建立', + 'settings.apiKeys.modal.createdTitle': 'API 金鑰已建立', + 'settings.apiKeys.modal.createdWarning': '請立即複製金鑰。它只會顯示一次,之後無法再取得。', + 'settings.apiKeys.modal.done': '完成', }; export default settings; diff --git a/shared/src/i18n/zh/settings.ts b/shared/src/i18n/zh/settings.ts index 23f544be3..acc9f3dba 100644 --- a/shared/src/i18n/zh/settings.ts +++ b/shared/src/i18n/zh/settings.ts @@ -492,6 +492,30 @@ const settings: TranslationStrings = { 'settings.pluginActivity.columns.status': '结果', 'settings.alwaysShowRoutes': '始终显示预订路线', 'settings.alwaysShowRoutesHint': '自动在地图上显示每个航班、火车和其他预订的路线,无需逐个手动开启。', + + // Public API keys (Settings -> Integrations) + 'settings.apiKeys.title': 'API 密钥', + 'settings.apiKeys.description': '公开 API 的密钥,让其他软件读取你的行程。只读:密钥无法修改或删除任何内容。', + 'settings.apiKeys.create': '创建密钥', + 'settings.apiKeys.empty': '还没有密钥。创建一个即可连接其他软件。', + 'settings.apiKeys.createdAt': '创建于', + 'settings.apiKeys.usedAt': '最近使用', + 'settings.apiKeys.deleteTitle': '删除密钥', + 'settings.apiKeys.deleteMessage': '使用该密钥的一切都会立即停止工作,且无法撤销。', + 'settings.apiKeys.deleted': '密钥已删除', + 'settings.apiKeys.deleteFailed': '无法删除密钥', + 'settings.apiKeys.createFailed': '无法创建密钥', + 'settings.apiKeys.copy': '复制', + 'settings.apiKeys.docsHint': '把密钥作为 "Authorization: Bearer ..." 或 "X-API-Key: ..." 发送到 /api/v1。', + 'settings.apiKeys.modal.createTitle': '创建 API 密钥', + 'settings.apiKeys.modal.name': '名称', + 'settings.apiKeys.modal.namePlaceholder': '例如 Dawarich', + 'settings.apiKeys.modal.nameHint': '仅供你自己识别,方便日后辨认这个密钥。', + 'settings.apiKeys.modal.creating': '创建中...', + 'settings.apiKeys.modal.create': '创建', + 'settings.apiKeys.modal.createdTitle': 'API 密钥已创建', + 'settings.apiKeys.modal.createdWarning': '请立即复制密钥。它只显示一次,之后无法再次获取。', + 'settings.apiKeys.modal.done': '完成', }; export default settings;