mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-22 06:56:35 +00:00
feat(assistant): trip assistant chat with your own AI connection
Adds an in-app assistant that reads and plans the open trip. Users connect their own provider (Sign in with ChatGPT, or an Anthropic, OpenAI, Gemini or OpenRouter key) or use the one the admin set up, including a custom OpenAI-compatible server. It is an addon and only shows while a connection is usable. The chat runs on the existing MCP tools, pinned to the trip. Every change is held as a change card the user applies or discards, and applied changes can be undone. On the desktop it is a floating window that can be moved and resized; on the phone it is fullscreen. Search results show as place cards that can go onto a day, weather lookups as a strip, proposed places as ghost markers on the map with a before/after switch, and an answer can be shared into the trip chat. Booking files can be dropped in and are read into change cards.
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
import {
|
||||
aiAdminSettingsSchema,
|
||||
aiApiKeyConnectRequestSchema,
|
||||
aiChatgptFinishRequestSchema,
|
||||
aiInstanceConnectionRequestSchema,
|
||||
aiOpenrouterStartRequestSchema,
|
||||
aiRetentionSchema,
|
||||
aiStatusResponseSchema,
|
||||
} from './ai-connections.schema';
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
describe('aiRetentionSchema', () => {
|
||||
it('keeps chats for a number of days, or forever as its own mode', () => {
|
||||
expect(aiRetentionSchema.safeParse({ mode: 'days', days: 90 }).success).toBe(true);
|
||||
expect(aiRetentionSchema.safeParse({ mode: 'forever' }).success).toBe(true);
|
||||
});
|
||||
|
||||
it('has no sentinel day count', () => {
|
||||
expect(aiRetentionSchema.safeParse({ mode: 'days', days: 0 }).success).toBe(false);
|
||||
expect(aiRetentionSchema.safeParse({ mode: 'days', days: -1 }).success).toBe(false);
|
||||
expect(aiRetentionSchema.safeParse({ mode: 'days', days: 3651 }).success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('aiApiKeyConnectRequestSchema', () => {
|
||||
it('takes a key for a cloud provider', () => {
|
||||
expect(aiApiKeyConnectRequestSchema.safeParse({ kind: 'anthropic', apiKey: 'sk-ant-api03-abcdef' }).success).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it('never lets a user connect ChatGPT by key or a server of their own', () => {
|
||||
expect(aiApiKeyConnectRequestSchema.safeParse({ kind: 'chatgpt', apiKey: 'abcdefghij' }).success).toBe(false);
|
||||
expect(aiApiKeyConnectRequestSchema.safeParse({ kind: 'custom', apiKey: 'abcdefghij' }).success).toBe(false);
|
||||
expect(
|
||||
aiApiKeyConnectRequestSchema.safeParse({ kind: 'openai', apiKey: 'sk-abcdefgh', baseUrl: 'http://10.0.0.2' })
|
||||
.success,
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('refuses a key too short to be one', () => {
|
||||
expect(aiApiKeyConnectRequestSchema.safeParse({ kind: 'gemini', apiKey: 'AIza' }).success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('aiInstanceConnectionRequestSchema', () => {
|
||||
it('takes an own server with a base URL and a model, the key optional', () => {
|
||||
expect(
|
||||
aiInstanceConnectionRequestSchema.safeParse({
|
||||
kind: 'custom',
|
||||
baseUrl: 'http://ollama:11434/v1',
|
||||
model: 'qwen3:8b',
|
||||
}).success,
|
||||
).toBe(true);
|
||||
expect(
|
||||
aiInstanceConnectionRequestSchema.safeParse({ kind: 'custom', baseUrl: 'http://ollama:11434' }).success,
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('refuses a base URL that is not http(s)', () => {
|
||||
expect(
|
||||
aiInstanceConnectionRequestSchema.safeParse({ kind: 'custom', baseUrl: 'javascript:alert(1)', model: 'm' })
|
||||
.success,
|
||||
).toBe(false);
|
||||
expect(
|
||||
aiInstanceConnectionRequestSchema.safeParse({ kind: 'custom', baseUrl: 'file:///etc/passwd', model: 'm' })
|
||||
.success,
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('gives a cloud key no base URL to redirect it to', () => {
|
||||
expect(aiInstanceConnectionRequestSchema.safeParse({ kind: 'openrouter', apiKey: 'sk-or-v1-abc' }).success).toBe(
|
||||
true,
|
||||
);
|
||||
expect(
|
||||
aiInstanceConnectionRequestSchema.safeParse({
|
||||
kind: 'anthropic',
|
||||
apiKey: 'sk-ant-api03-abcdef',
|
||||
baseUrl: 'https://evil.example',
|
||||
}).success,
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('never shares a ChatGPT plan as the instance connection', () => {
|
||||
expect(aiInstanceConnectionRequestSchema.safeParse({ kind: 'chatgpt', apiKey: 'abcdefghij' }).success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('aiChatgptFinishRequestSchema', () => {
|
||||
it('needs the flow id it was started with', () => {
|
||||
const pastedUrl = 'http://127.0.0.1:1455/auth/callback?code=c&state=s';
|
||||
expect(
|
||||
aiChatgptFinishRequestSchema.safeParse({ flowId: '0d6c1f0e-2c3b-4a5d-8e9f-1a2b3c4d5e6f', pastedUrl }).success,
|
||||
).toBe(true);
|
||||
expect(aiChatgptFinishRequestSchema.safeParse({ flowId: 'flow-1', pastedUrl }).success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('aiOpenrouterStartRequestSchema', () => {
|
||||
it('takes only the scope, never a callback address', () => {
|
||||
expect(aiOpenrouterStartRequestSchema.safeParse({ scope: 'user' }).success).toBe(true);
|
||||
expect(
|
||||
aiOpenrouterStartRequestSchema.safeParse({ scope: 'user', callbackUrl: 'https://evil.example' }).success,
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('aiAdminSettingsSchema', () => {
|
||||
it('takes the policy, the daily limit and the retention', () => {
|
||||
expect(
|
||||
aiAdminSettingsSchema.safeParse({
|
||||
policy: 'instance_or_user',
|
||||
dailyTurns: 50,
|
||||
retention: { mode: 'days', days: 90 },
|
||||
}).success,
|
||||
).toBe(true);
|
||||
expect(
|
||||
aiAdminSettingsSchema.safeParse({ policy: 'instance', dailyTurns: 0, retention: { mode: 'forever' } }).success,
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('aiStatusResponseSchema', () => {
|
||||
it('describes a user on the instance connection with a quota', () => {
|
||||
expect(
|
||||
aiStatusResponseSchema.safeParse({
|
||||
available: true,
|
||||
policy: 'instance_or_user',
|
||||
canConnectOwn: true,
|
||||
effective: {
|
||||
source: 'instance',
|
||||
kind: 'anthropic',
|
||||
model: 'claude-sonnet-4-5',
|
||||
host: 'api.anthropic.com',
|
||||
paidBy: 'instance',
|
||||
},
|
||||
own: null,
|
||||
instance: { kind: 'anthropic', model: 'claude-sonnet-4-5', host: 'api.anthropic.com' },
|
||||
quota: { used: 3, limit: 50 },
|
||||
privacyAcked: false,
|
||||
retention: { mode: 'days', days: 90 },
|
||||
openrouterOAuth: true,
|
||||
}).success,
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,219 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* AI connections contract for /api/ai (a user's own provider) and /api/admin/ai
|
||||
* (the connection the instance provides for everyone, plus the assistant's
|
||||
* admin settings).
|
||||
*
|
||||
* A connection is a provider kind, a model and a sealed credential. The
|
||||
* credential never leaves the server: every view carries a `hint` (the last
|
||||
* characters of a key, or the e-mail of a ChatGPT account) instead. Two kinds
|
||||
* are bound to one side: `chatgpt` is a personal plan and never shared as the
|
||||
* instance connection, `custom` reaches an arbitrary base URL and is admin-only
|
||||
* (#1772). The server and the database both refuse the other combination.
|
||||
*/
|
||||
|
||||
export const aiConnectionKindSchema = z.enum(['anthropic', 'openai', 'gemini', 'openrouter', 'chatgpt', 'custom']);
|
||||
export type AiConnectionKind = z.infer<typeof aiConnectionKindSchema>;
|
||||
|
||||
/** The kinds a pasted API key can belong to. */
|
||||
export const aiApiKeyKindSchema = z.enum(['anthropic', 'openai', 'gemini', 'openrouter']);
|
||||
export type AiApiKeyKind = z.infer<typeof aiApiKeyKindSchema>;
|
||||
|
||||
/**
|
||||
* Who provides the connection: `instance` only the admin's, `instance_or_user`
|
||||
* the admin's unless a user connects their own, `user` everyone their own.
|
||||
*/
|
||||
export const aiPolicySchema = z.enum(['instance', 'instance_or_user', 'user']);
|
||||
export type AiPolicy = z.infer<typeof aiPolicySchema>;
|
||||
|
||||
/** How long assistant chats are kept. Keeping them forever is its own mode, not a day count. */
|
||||
export const aiRetentionSchema = z.discriminatedUnion('mode', [
|
||||
z.object({ mode: z.literal('days'), days: z.number().int().min(1).max(3650) }),
|
||||
z.object({ mode: z.literal('forever') }),
|
||||
]);
|
||||
export type AiRetention = z.infer<typeof aiRetentionSchema>;
|
||||
|
||||
export const aiConnectionScopeSchema = z.enum(['instance', 'user']);
|
||||
export type AiConnectionScope = z.infer<typeof aiConnectionScopeSchema>;
|
||||
|
||||
export const aiConnectionStatusSchema = z.enum(['ok', 'needs_reauth', 'signed_out', 'error']);
|
||||
export type AiConnectionStatus = z.infer<typeof aiConnectionStatusSchema>;
|
||||
|
||||
/** One model a connection offers. Only models that can call tools are listed for the assistant. */
|
||||
export const aiModelInfoSchema = z.object({
|
||||
id: z.string(),
|
||||
label: z.string(),
|
||||
supportsTools: z.boolean(),
|
||||
free: z.boolean().optional(),
|
||||
// The one a new connection starts on: the newest stable model of a capable line. Listed first.
|
||||
recommended: z.boolean().optional(),
|
||||
});
|
||||
export type AiModelInfo = z.infer<typeof aiModelInfoSchema>;
|
||||
|
||||
/** A stored connection as the client sees it, without its credential. */
|
||||
export const aiConnectionViewSchema = z.object({
|
||||
scope: aiConnectionScopeSchema,
|
||||
kind: aiConnectionKindSchema,
|
||||
model: z.string(),
|
||||
// "…a1b2" for a key, the account e-mail for ChatGPT; never the secret itself.
|
||||
hint: z.string().nullable(),
|
||||
status: aiConnectionStatusSchema,
|
||||
// The provider host the conversation goes to, named in the privacy notice.
|
||||
host: z.string(),
|
||||
});
|
||||
export type AiConnectionView = z.infer<typeof aiConnectionViewSchema>;
|
||||
|
||||
// ── User: /api/ai ───────────────────────────────────────────────────────────
|
||||
|
||||
/** GET /api/ai/status: whether the assistant can run for this user, and on whose connection. */
|
||||
export const aiStatusResponseSchema = z.object({
|
||||
// False whenever no usable connection exists under the policy (fail-closed).
|
||||
available: z.boolean(),
|
||||
policy: aiPolicySchema,
|
||||
// policy !== 'instance' and not the demo user.
|
||||
canConnectOwn: z.boolean(),
|
||||
effective: z
|
||||
.object({
|
||||
source: aiConnectionScopeSchema,
|
||||
kind: aiConnectionKindSchema,
|
||||
model: z.string(),
|
||||
host: z.string(),
|
||||
paidBy: aiConnectionScopeSchema,
|
||||
})
|
||||
.nullable(),
|
||||
own: aiConnectionViewSchema.nullable(),
|
||||
instance: z.object({ kind: aiConnectionKindSchema, model: z.string(), host: z.string() }).nullable(),
|
||||
// Today's turns against the daily limit, only while the instance pays.
|
||||
quota: z.object({ used: z.number().int(), limit: z.number().int() }).nullable(),
|
||||
// Whether the user acknowledged that the effective kind@host receives their data.
|
||||
privacyAcked: z.boolean(),
|
||||
retention: aiRetentionSchema,
|
||||
// OpenRouter's OAuth needs an https callback; without one the client offers the key field instead.
|
||||
openrouterOAuth: z.boolean(),
|
||||
});
|
||||
export type AiStatusResponse = z.infer<typeof aiStatusResponseSchema>;
|
||||
|
||||
/** PUT /api/ai/connection: saving tests the key against the provider's model list first. */
|
||||
export const aiApiKeyConnectRequestSchema = z
|
||||
.object({
|
||||
kind: aiApiKeyKindSchema,
|
||||
apiKey: z.string().min(8).max(400),
|
||||
model: z.string().max(200).optional(),
|
||||
})
|
||||
.strict();
|
||||
export type AiApiKeyConnectRequest = z.infer<typeof aiApiKeyConnectRequestSchema>;
|
||||
|
||||
/** PATCH /api/ai/connection/model */
|
||||
export const aiModelUpdateRequestSchema = z.object({ model: z.string().min(1).max(200) }).strict();
|
||||
export type AiModelUpdateRequest = z.infer<typeof aiModelUpdateRequestSchema>;
|
||||
|
||||
/** POST /api/ai/connect/chatgpt/finish: the loopback address the browser landed on after sign-in. */
|
||||
export const aiChatgptFinishRequestSchema = z
|
||||
.object({
|
||||
flowId: z.uuid(),
|
||||
pastedUrl: z.string().max(4096),
|
||||
})
|
||||
.strict();
|
||||
export type AiChatgptFinishRequest = z.infer<typeof aiChatgptFinishRequestSchema>;
|
||||
|
||||
/** POST /api/ai/connect/openrouter/start and /api/admin/ai/connect/openrouter/start */
|
||||
export const aiOpenrouterStartRequestSchema = z.object({ scope: aiConnectionScopeSchema }).strict();
|
||||
export type AiOpenrouterStartRequest = z.infer<typeof aiOpenrouterStartRequestSchema>;
|
||||
|
||||
/** POST /api/ai/connect/openrouter/finish */
|
||||
export const aiOpenrouterFinishRequestSchema = z
|
||||
.object({
|
||||
flowId: z.uuid(),
|
||||
code: z.string().min(1).max(512),
|
||||
})
|
||||
.strict();
|
||||
export type AiOpenrouterFinishRequest = z.infer<typeof aiOpenrouterFinishRequestSchema>;
|
||||
|
||||
/** The answer of every connect/<provider>/start: where to send the browser, and the flow to finish. */
|
||||
export const aiAuthStartResponseSchema = z.object({
|
||||
flowId: z.uuid(),
|
||||
authorizeUrl: z.url(),
|
||||
expiresAt: z.string(),
|
||||
});
|
||||
export type AiAuthStartResponse = z.infer<typeof aiAuthStartResponseSchema>;
|
||||
|
||||
/** GET /api/ai/models, GET /api/admin/ai/models */
|
||||
export const aiModelListResponseSchema = z.object({ models: z.array(aiModelInfoSchema) });
|
||||
export type AiModelListResponse = z.infer<typeof aiModelListResponseSchema>;
|
||||
|
||||
/** POST /api/ai/privacy-ack: the user saw that kind@host receives their messages. */
|
||||
export const aiPrivacyAckRequestSchema = z
|
||||
.object({
|
||||
kind: aiConnectionKindSchema,
|
||||
host: z.string().max(253),
|
||||
})
|
||||
.strict();
|
||||
export type AiPrivacyAckRequest = z.infer<typeof aiPrivacyAckRequestSchema>;
|
||||
|
||||
// ── Admin: /api/admin/ai ────────────────────────────────────────────────────
|
||||
|
||||
// z.url() alone would accept javascript: and file:.
|
||||
const httpUrl = z.url({ protocol: /^https?$/ });
|
||||
|
||||
const instanceApiKeyConnection = <K extends AiApiKeyKind>(kind: K) =>
|
||||
z
|
||||
.object({
|
||||
kind: z.literal(kind),
|
||||
apiKey: z.string().min(8).max(400),
|
||||
model: z.string().max(200).optional(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
/** PUT /api/admin/ai/connection: a cloud key, or an OpenAI-compatible server of the admin's own. */
|
||||
export const aiInstanceConnectionRequestSchema = z.discriminatedUnion('kind', [
|
||||
z
|
||||
.object({
|
||||
kind: z.literal('custom'),
|
||||
baseUrl: httpUrl.max(500),
|
||||
apiKey: z.string().max(400).optional(),
|
||||
model: z.string().min(1).max(200),
|
||||
})
|
||||
.strict(),
|
||||
instanceApiKeyConnection('anthropic'),
|
||||
instanceApiKeyConnection('openai'),
|
||||
instanceApiKeyConnection('gemini'),
|
||||
instanceApiKeyConnection('openrouter'),
|
||||
]);
|
||||
export type AiInstanceConnectionRequest = z.infer<typeof aiInstanceConnectionRequestSchema>;
|
||||
|
||||
/** What the AI parsing addon is configured with, offered to prefill the instance connection. */
|
||||
export const aiParsingPrefillSchema = z.object({
|
||||
kind: aiConnectionKindSchema,
|
||||
baseUrl: z.string().nullable(),
|
||||
model: z.string(),
|
||||
});
|
||||
export type AiParsingPrefill = z.infer<typeof aiParsingPrefillSchema>;
|
||||
|
||||
/** GET /api/admin/ai/connection */
|
||||
export const aiAdminConnectionResponseSchema = z.object({
|
||||
connection: aiConnectionViewSchema.extend({ baseUrl: z.string().nullable() }).nullable(),
|
||||
// Only while no instance connection exists and AI parsing is configured.
|
||||
prefill: aiParsingPrefillSchema.nullable(),
|
||||
});
|
||||
export type AiAdminConnectionResponse = z.infer<typeof aiAdminConnectionResponseSchema>;
|
||||
|
||||
/** GET and PUT /api/admin/ai/settings */
|
||||
export const aiAdminSettingsSchema = z
|
||||
.object({
|
||||
policy: aiPolicySchema,
|
||||
dailyTurns: z.number().int().min(1).max(10000),
|
||||
retention: aiRetentionSchema,
|
||||
})
|
||||
.strict();
|
||||
export type AiAdminSettings = z.infer<typeof aiAdminSettingsSchema>;
|
||||
|
||||
// ── Claude connector: /api/integrations/claude-connector ───────────────────
|
||||
|
||||
/** GET /api/integrations/claude-connector: adding TREK's MCP server in Claude, as a link and a QR code. */
|
||||
export const aiClaudeConnectorResponseSchema = z.object({
|
||||
mcpUrl: z.url(),
|
||||
deepLink: z.url(),
|
||||
qrSvg: z.string(),
|
||||
});
|
||||
export type AiClaudeConnectorResponse = z.infer<typeof aiClaudeConnectorResponseSchema>;
|
||||
@@ -0,0 +1,38 @@
|
||||
data: {"type":"start","messageId":"msg-12"}
|
||||
|
||||
data: {"type":"data-trek-thread","data":{"threadId":7,"title":"Fit the Golden Pavilion into Friday"}}
|
||||
|
||||
data: {"type":"start-step"}
|
||||
|
||||
data: {"type":"tool-input-start","toolCallId":"toolu_01","toolName":"get_trip_summary"}
|
||||
|
||||
data: {"type":"tool-input-available","toolCallId":"toolu_01","toolName":"get_trip_summary","input":{}}
|
||||
|
||||
data: {"type":"tool-output-available","toolCallId":"toolu_01","output":{"title":"Kyoto in June","days":[{"id":31,"date":"2026-06-11","places":3},{"id":32,"date":"2026-06-12","places":1}]}}
|
||||
|
||||
data: {"type":"finish-step"}
|
||||
|
||||
data: {"type":"start-step"}
|
||||
|
||||
data: {"type":"text-start","id":"txt-0"}
|
||||
|
||||
data: {"type":"text-delta","id":"txt-0","delta":"Friday has room in the afternoon. "}
|
||||
|
||||
data: {"type":"text-delta","id":"txt-0","delta":"I would add Kinkaku-ji there."}
|
||||
|
||||
data: {"type":"text-end","id":"txt-0"}
|
||||
|
||||
data: {"type":"tool-input-start","toolCallId":"3f2b8c1e-6d4a-4f7b-9e2c-5a1d8b7c6e40","toolName":"create_and_assign_place"}
|
||||
|
||||
data: {"type":"tool-input-available","toolCallId":"3f2b8c1e-6d4a-4f7b-9e2c-5a1d8b7c6e40","toolName":"create_and_assign_place","input":{"dayId":32,"name":"Kinkaku-ji","address":"1 Kinkakujicho, Kita Ward, Kyoto","lat":35.0394,"lng":135.7292}}
|
||||
|
||||
data: {"type":"tool-approval-request","approvalId":"3f2b8c1e-6d4a-4f7b-9e2c-5a1d8b7c6e40","toolCallId":"3f2b8c1e-6d4a-4f7b-9e2c-5a1d8b7c6e40"}
|
||||
|
||||
data: {"type":"data-trek-change","data":{"toolCallId":"3f2b8c1e-6d4a-4f7b-9e2c-5a1d8b7c6e40","verbKey":"addPlaceToDay","subject":"Kinkaku-ji","rows":[{"op":"add","labelKey":"day","before":null,"after":"Day 2, Fri 12 Jun"},{"op":"add","labelKey":"address","before":null,"after":"1 Kinkakujicho, Kita Ward, Kyoto"}],"dayId":32,"placeIds":[],"undoable":true}}
|
||||
|
||||
data: {"type":"data-trek-quota","data":{"used":3,"limit":50}}
|
||||
|
||||
data: {"type":"finish","finishReason":"tool-calls"}
|
||||
|
||||
data: [DONE]
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { assistantShareRequestSchema } from './assistant-share.schema';
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
describe('assistantShareRequestSchema', () => {
|
||||
it('takes one to 5000 characters and trims them', () => {
|
||||
expect(assistantShareRequestSchema.parse({ text: ' Day 2 is set. ' })).toEqual({ text: 'Day 2 is set.' });
|
||||
expect(assistantShareRequestSchema.safeParse({ text: 'x' }).success).toBe(true);
|
||||
expect(assistantShareRequestSchema.safeParse({ text: 'x'.repeat(5000) }).success).toBe(true);
|
||||
});
|
||||
|
||||
it('refuses empty or blank text and text past the chat limit', () => {
|
||||
expect(assistantShareRequestSchema.safeParse({ text: '' }).success).toBe(false);
|
||||
expect(assistantShareRequestSchema.safeParse({ text: ' \n\t ' }).success).toBe(false);
|
||||
expect(assistantShareRequestSchema.safeParse({ text: 'x'.repeat(5001) }).success).toBe(false);
|
||||
expect(assistantShareRequestSchema.safeParse({}).success).toBe(false);
|
||||
});
|
||||
|
||||
it('takes nothing but the text, so the origin is never the client to set', () => {
|
||||
expect(assistantShareRequestSchema.safeParse({ text: 'hi', origin: 'assistant' }).success).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* POST /api/trips/:tripId/assistant/threads/:threadId/messages/:messageId/share:
|
||||
* posts the text of an assistant answer to the trip's chat, marked as shared
|
||||
* from the assistant. The text is what the user saw in the preview; the
|
||||
* server checks that the answer is theirs and in this trip, not the words.
|
||||
* It answers with the chat message (`collabMessageResponseSchema`).
|
||||
*/
|
||||
export const assistantShareRequestSchema = z
|
||||
.object({
|
||||
text: z.string().trim().min(1).max(5000),
|
||||
})
|
||||
.strict();
|
||||
export type AssistantShareRequest = z.infer<typeof assistantShareRequestSchema>;
|
||||
@@ -0,0 +1,430 @@
|
||||
/// <reference types="node" />
|
||||
import {
|
||||
ASSISTANT_CHANGE_LABEL_KEYS,
|
||||
assistantAddFoundPlaceRequestSchema,
|
||||
assistantAddFoundPlaceResponseSchema,
|
||||
assistantAttachmentRequestSchema,
|
||||
assistantChangePreviewSchema,
|
||||
assistantChatRequestSchema,
|
||||
assistantDecisionsRequestSchema,
|
||||
assistantFoundPlaceSchema,
|
||||
assistantMessageSchema,
|
||||
assistantPlacesSchema,
|
||||
assistantStopRequestSchema,
|
||||
assistantWeatherSchema,
|
||||
UI_STREAM_DONE,
|
||||
uiStreamChunkSchema,
|
||||
type UiStreamChunk,
|
||||
} from './assistant.schema';
|
||||
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
const ctx = { locale: 'de', timeZone: 'Europe/Berlin' };
|
||||
|
||||
/** The `data:` payloads of an SSE body, in order. */
|
||||
const dataEvents = (body: string) =>
|
||||
body
|
||||
.split('\n\n')
|
||||
.filter((event) => event.startsWith('data: '))
|
||||
.map((event) => event.slice('data: '.length));
|
||||
|
||||
const fixture = readFileSync(fileURLToPath(new URL('./__fixtures__/ui-stream.v1.txt', import.meta.url)), 'utf8');
|
||||
|
||||
describe('assistantChatRequestSchema', () => {
|
||||
it('takes a first message without a thread and trims the text', () => {
|
||||
const parsed = assistantChatRequestSchema.parse({ ...ctx, threadId: null, text: ' Plan day 2 ' });
|
||||
expect(parsed.text).toBe('Plan day 2');
|
||||
});
|
||||
|
||||
it('refuses blank or overlong text, a bad thread id and unknown keys', () => {
|
||||
expect(assistantChatRequestSchema.safeParse({ ...ctx, threadId: null, text: ' ' }).success).toBe(false);
|
||||
expect(assistantChatRequestSchema.safeParse({ ...ctx, threadId: null, text: 'x'.repeat(4001) }).success).toBe(
|
||||
false,
|
||||
);
|
||||
expect(assistantChatRequestSchema.safeParse({ ...ctx, threadId: 0, text: 'hi' }).success).toBe(false);
|
||||
expect(assistantChatRequestSchema.safeParse({ ...ctx, threadId: null, text: 'hi', tripId: 9 }).success).toBe(false);
|
||||
});
|
||||
|
||||
it('carries a focus of at most 20 places', () => {
|
||||
const many = Array.from({ length: 21 }, (_, i) => i + 1);
|
||||
expect(
|
||||
assistantChatRequestSchema.safeParse({ ...ctx, threadId: 3, text: 'hi', focus: { dayId: 4, placeIds: [1] } })
|
||||
.success,
|
||||
).toBe(true);
|
||||
expect(
|
||||
assistantChatRequestSchema.safeParse({ ...ctx, threadId: 3, text: 'hi', focus: { placeIds: many } }).success,
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('needs the locale and time zone', () => {
|
||||
expect(assistantChatRequestSchema.safeParse({ threadId: null, text: 'hi' }).success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('assistantAttachmentRequestSchema', () => {
|
||||
it('reads the multipart text fields, the thread id from its text', () => {
|
||||
expect(assistantAttachmentRequestSchema.parse({ ...ctx })).toEqual(ctx);
|
||||
expect(assistantAttachmentRequestSchema.parse({ ...ctx, threadId: '12' })).toEqual({ ...ctx, threadId: 12 });
|
||||
});
|
||||
|
||||
it('refuses a bad thread id, unknown fields and a missing context', () => {
|
||||
for (const threadId of ['0', '-1', 'abc', '1.5']) {
|
||||
expect(assistantAttachmentRequestSchema.safeParse({ ...ctx, threadId }).success, threadId).toBe(false);
|
||||
}
|
||||
expect(assistantAttachmentRequestSchema.safeParse({ ...ctx, text: 'hi' }).success).toBe(false);
|
||||
expect(assistantAttachmentRequestSchema.safeParse({ threadId: '3' }).success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('assistantDecisionsRequestSchema', () => {
|
||||
it('takes one to twelve decisions', () => {
|
||||
const decision = { toolCallId: 'a', approved: true };
|
||||
expect(assistantDecisionsRequestSchema.safeParse({ ...ctx, decisions: [decision] }).success).toBe(true);
|
||||
expect(assistantDecisionsRequestSchema.safeParse({ ...ctx, decisions: [] }).success).toBe(false);
|
||||
expect(
|
||||
assistantDecisionsRequestSchema.safeParse({ ...ctx, decisions: Array.from({ length: 13 }, () => decision) })
|
||||
.success,
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('never takes arguments for a call, only the decision', () => {
|
||||
expect(
|
||||
assistantDecisionsRequestSchema.safeParse({
|
||||
...ctx,
|
||||
decisions: [{ toolCallId: 'a', approved: true, args: { name: 'x' } }],
|
||||
}).success,
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('assistantStopRequestSchema', () => {
|
||||
it('is an empty JSON object', () => {
|
||||
expect(assistantStopRequestSchema.safeParse({}).success).toBe(true);
|
||||
expect(assistantStopRequestSchema.safeParse({ force: true }).success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('assistantChangePreviewSchema', () => {
|
||||
const preview = {
|
||||
toolCallId: 'c1',
|
||||
verbKey: 'addPlace',
|
||||
subject: 'Kinkaku-ji',
|
||||
rows: [{ op: 'add', labelKey: 'address', before: null, after: 'Kyoto' }],
|
||||
dayId: null,
|
||||
placeIds: [],
|
||||
undoable: true,
|
||||
};
|
||||
|
||||
it('labels rows from the closed key list only', () => {
|
||||
expect(assistantChangePreviewSchema.safeParse(preview).success).toBe(true);
|
||||
expect(
|
||||
assistantChangePreviewSchema.safeParse({ ...preview, rows: [{ ...preview.rows[0], labelKey: 'evil' }] }).success,
|
||||
).toBe(false);
|
||||
expect(assistantChangePreviewSchema.safeParse({ ...preview, verbKey: 'deleteTrip' }).success).toBe(false);
|
||||
});
|
||||
|
||||
it('holds at most twelve rows', () => {
|
||||
const rows = ASSISTANT_CHANGE_LABEL_KEYS.slice(0, 13).map((labelKey) => ({
|
||||
op: 'add',
|
||||
labelKey,
|
||||
before: null,
|
||||
after: 'x',
|
||||
}));
|
||||
expect(assistantChangePreviewSchema.safeParse({ ...preview, rows }).success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('assistantMessageSchema', () => {
|
||||
it('reads a stored message with an expired approval', () => {
|
||||
const parsed = assistantMessageSchema.safeParse({
|
||||
id: '12',
|
||||
role: 'assistant',
|
||||
status: 'awaiting_approval',
|
||||
createdAt: '2026-10-21T08:00:00.000Z',
|
||||
parts: [
|
||||
{ type: 'text', text: 'Adding it.' },
|
||||
{
|
||||
type: 'tool-call',
|
||||
toolCallId: 'c1',
|
||||
toolName: 'create_place',
|
||||
args: { name: 'Kinkaku-ji' },
|
||||
approval: { id: 'c1', resolution: 'expired' },
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(parsed.success).toBe(true);
|
||||
});
|
||||
|
||||
it('reads a user message that attached a file, by its name, type and size only', () => {
|
||||
const message = { id: '13', role: 'user', status: 'complete', createdAt: '2026-10-21T08:00:00.000Z' };
|
||||
const attachment = { type: 'attachment', name: 'ticket.pdf', mime: 'application/pdf', size: 2048 };
|
||||
expect(assistantMessageSchema.safeParse({ ...message, parts: [attachment] }).success).toBe(true);
|
||||
const negative = { ...attachment, size: -1 };
|
||||
expect(assistantMessageSchema.safeParse({ ...message, parts: [negative] }).success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('uiStreamChunkSchema', () => {
|
||||
it('reads an unknown error code as generic', () => {
|
||||
expect(uiStreamChunkSchema.parse({ type: 'error', errorText: 'something_new' })).toEqual({
|
||||
type: 'error',
|
||||
errorText: 'generic',
|
||||
});
|
||||
expect(uiStreamChunkSchema.parse({ type: 'error', errorText: 'daily_limit' })).toEqual({
|
||||
type: 'error',
|
||||
errorText: 'daily_limit',
|
||||
});
|
||||
});
|
||||
|
||||
it('refuses a chunk type it does not know', () => {
|
||||
expect(uiStreamChunkSchema.safeParse({ type: 'reasoning-delta', id: 'r', delta: 'x' }).success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ui-stream.v1 fixture', () => {
|
||||
const events = dataEvents(fixture);
|
||||
const chunks = events.slice(0, -1).map((event) => uiStreamChunkSchema.parse(JSON.parse(event)));
|
||||
const ofType = <T extends UiStreamChunk['type']>(type: T) =>
|
||||
chunks.filter((c): c is Extract<UiStreamChunk, { type: T }> => c.type === type);
|
||||
|
||||
it('is a stream of valid chunks closed by [DONE]', () => {
|
||||
expect(events.at(-1)).toBe(UI_STREAM_DONE);
|
||||
expect(chunks[0]?.type).toBe('start');
|
||||
expect(chunks.at(-1)).toEqual({ type: 'finish', finishReason: 'tool-calls' });
|
||||
});
|
||||
|
||||
it('names the new thread before anything else is said', () => {
|
||||
expect(chunks[1]?.type).toBe('data-trek-thread');
|
||||
});
|
||||
|
||||
it('asks for approval with the call id and previews that same call', () => {
|
||||
const [request] = ofType('tool-approval-request');
|
||||
const [change] = ofType('data-trek-change');
|
||||
expect(request).toBeDefined();
|
||||
expect(request?.approvalId).toBe(request?.toolCallId);
|
||||
expect(change?.data.toolCallId).toBe(request?.toolCallId);
|
||||
expect(ofType('tool-input-available').map((c) => c.toolCallId)).toContain(request?.toolCallId);
|
||||
});
|
||||
|
||||
it('closes every text block it opens', () => {
|
||||
const opened = ofType('text-start').map((c) => c.id);
|
||||
expect(opened.length).toBeGreaterThan(0);
|
||||
expect(ofType('text-end').map((c) => c.id)).toEqual(opened);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mentions in a chat request', () => {
|
||||
const base = { ...ctx, threadId: 3, text: 'Move @Louvre to @Friday' };
|
||||
|
||||
it('names up to ten days, places or bookings by id, never by label', () => {
|
||||
const mentions = [
|
||||
{ kind: 'place', id: 4 },
|
||||
{ kind: 'day', id: 9 },
|
||||
{ kind: 'reservation', id: 2 },
|
||||
];
|
||||
expect(assistantChatRequestSchema.safeParse({ ...base, mentions }).success).toBe(true);
|
||||
const eleven = Array.from({ length: 11 }, () => ({ kind: 'place', id: 4 }));
|
||||
expect(assistantChatRequestSchema.safeParse({ ...base, mentions: eleven }).success).toBe(false);
|
||||
const labelled = [{ kind: 'place', id: 4, label: 'Louvre' }];
|
||||
expect(assistantChatRequestSchema.safeParse({ ...base, mentions: labelled }).success).toBe(false);
|
||||
expect(assistantChatRequestSchema.safeParse({ ...base, mentions: [{ kind: 'trip', id: 4 }] }).success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('assistantAddFoundPlaceRequestSchema', () => {
|
||||
const parse = (body: Record<string, unknown>) => assistantAddFoundPlaceRequestSchema.safeParse({ ...ctx, ...body });
|
||||
|
||||
it('names a found place by its ref and the day it goes on', () => {
|
||||
expect(parse({ ref: 'toolu_01:2', dayId: 32 }).success).toBe(true);
|
||||
});
|
||||
|
||||
it('refuses an empty or overlong ref, a bad day and anything about the place itself', () => {
|
||||
expect(parse({ ref: '', dayId: 32 }).success).toBe(false);
|
||||
expect(parse({ ref: 'x'.repeat(81), dayId: 32 }).success).toBe(false);
|
||||
expect(parse({ ref: 'a:1', dayId: 0 }).success).toBe(false);
|
||||
expect(parse({ ref: 'a:1', dayId: 3, name: 'Elsewhere' }).success).toBe(false);
|
||||
expect(assistantAddFoundPlaceRequestSchema.safeParse({ ref: 'a:1', dayId: 3 }).success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('the effect of a change', () => {
|
||||
const preview = {
|
||||
toolCallId: 'c1',
|
||||
verbKey: 'addPlaceToDay',
|
||||
subject: 'Kinkaku-ji',
|
||||
rows: [],
|
||||
dayId: 32,
|
||||
placeIds: [],
|
||||
undoable: true,
|
||||
};
|
||||
const ghost = { name: 'Kinkaku-ji', lat: 35.0394, lng: 135.7292, categoryId: null };
|
||||
const add = { kind: 'add', dayId: 32, fromDayId: null, placeId: null, assignmentId: null, place: ghost };
|
||||
const withEffect = (effect: unknown) => assistantChangePreviewSchema.safeParse({ ...preview, effect }).success;
|
||||
|
||||
it('is optional, so previews stored before it still read', () => {
|
||||
expect(assistantChangePreviewSchema.safeParse(preview).success).toBe(true);
|
||||
expect(withEffect(null)).toBe(true);
|
||||
});
|
||||
|
||||
it('places a new place on its day, or moves an existing one between days', () => {
|
||||
expect(withEffect(add)).toBe(true);
|
||||
const existing = { placeId: 7, assignmentId: 12, place: null };
|
||||
expect(withEffect({ kind: 'move', dayId: 32, fromDayId: 31, ...existing })).toBe(true);
|
||||
expect(withEffect({ kind: 'remove', dayId: 31, fromDayId: null, ...existing })).toBe(true);
|
||||
});
|
||||
|
||||
it('refuses a position off the globe, an overlong name and an unknown kind', () => {
|
||||
expect(withEffect({ ...add, place: { ...ghost, lat: 91 } })).toBe(false);
|
||||
expect(withEffect({ ...add, place: { ...ghost, lng: -181 } })).toBe(false);
|
||||
expect(withEffect({ ...add, place: { ...ghost, name: 'x'.repeat(201) } })).toBe(false);
|
||||
expect(withEffect({ ...add, kind: 'swap' })).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('assistantFoundPlaceSchema', () => {
|
||||
const place = {
|
||||
ref: 'toolu_01:0',
|
||||
name: 'Trattoria da Enzo',
|
||||
address: 'Via dei Vascellari 29, Roma',
|
||||
lat: 41.8885,
|
||||
lng: 12.4771,
|
||||
kind: 'food',
|
||||
rating: 4.6,
|
||||
openNow: null,
|
||||
googlePlaceId: 'ChIJ-abc_123',
|
||||
osmId: null,
|
||||
photoKey: 'ChIJ-abc_123',
|
||||
inTripPlaceId: null,
|
||||
};
|
||||
const withPlace = (patch: Record<string, unknown>) => assistantFoundPlaceSchema.safeParse({ ...place, ...patch });
|
||||
|
||||
it('reads a place with a source id as its photo key, or coordinates', () => {
|
||||
expect(withPlace({}).success).toBe(true);
|
||||
expect(withPlace({ photoKey: 'coords:41.8885,12.4771' }).success).toBe(true);
|
||||
expect(withPlace({ photoKey: 'coords:-33.9,-70.6' }).success).toBe(true);
|
||||
expect(withPlace({ photoKey: null }).success).toBe(true);
|
||||
});
|
||||
|
||||
it('never takes a URL or anything with a path as the photo key', () => {
|
||||
const notKeys = [
|
||||
'https://evil.example/p.jpg',
|
||||
'//evil.example/p.jpg',
|
||||
'/api/maps/place-photo/x/bytes',
|
||||
'a/b',
|
||||
'coords:1,2;x',
|
||||
'',
|
||||
];
|
||||
for (const photoKey of notKeys) expect(withPlace({ photoKey }).success, photoKey).toBe(false);
|
||||
});
|
||||
|
||||
it('refuses a Google id that is not one, a rating past five and an unknown kind', () => {
|
||||
expect(withPlace({ googlePlaceId: 'a b' }).success).toBe(false);
|
||||
expect(withPlace({ rating: 5.1 }).success).toBe(false);
|
||||
expect(withPlace({ kind: 'casino' }).success).toBe(false);
|
||||
});
|
||||
|
||||
it('holds at most eight places to a step and counts the rest', () => {
|
||||
const places = Array.from({ length: 9 }, (_, i) => ({ ...place, ref: `toolu_01:${i}` }));
|
||||
const step = (body: Record<string, unknown>) =>
|
||||
assistantPlacesSchema.safeParse({ toolCallId: 'toolu_01', ...body });
|
||||
expect(step({ places: places.slice(0, 8), more: 1 }).success).toBe(true);
|
||||
expect(step({ places, more: 0 }).success).toBe(false);
|
||||
expect(step({ places: [], more: -1 }).success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('assistantWeatherSchema', () => {
|
||||
const day = {
|
||||
dayId: 32,
|
||||
date: '2026-06-12',
|
||||
main: 'Clear',
|
||||
tempMax: 24.5,
|
||||
tempMin: 16,
|
||||
rainPct: 10,
|
||||
kind: 'forecast',
|
||||
};
|
||||
|
||||
it('reads one to sixteen days, a climate day without a low or a rain chance', () => {
|
||||
expect(assistantWeatherSchema.safeParse({ days: [day] }).success).toBe(true);
|
||||
const climate = { ...day, dayId: null, tempMin: null, rainPct: null, kind: 'climate' };
|
||||
expect(assistantWeatherSchema.safeParse({ days: [climate] }).success).toBe(true);
|
||||
expect(assistantWeatherSchema.safeParse({ days: [] }).success).toBe(false);
|
||||
expect(assistantWeatherSchema.safeParse({ days: Array.from({ length: 17 }, () => day) }).success).toBe(false);
|
||||
});
|
||||
|
||||
it('refuses a date that is not a calendar date and a rain chance past 100', () => {
|
||||
expect(assistantWeatherSchema.safeParse({ days: [{ ...day, date: '12.06.2026' }] }).success).toBe(false);
|
||||
expect(assistantWeatherSchema.safeParse({ days: [{ ...day, rainPct: 101 }] }).success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('the new message parts', () => {
|
||||
const message = { id: '14', status: 'complete', createdAt: '2026-10-21T08:00:00.000Z' };
|
||||
const rainyDay = { dayId: 32, date: '2026-06-12', main: 'Rain', tempMax: 18, tempMin: 12, rainPct: 80 };
|
||||
|
||||
it('keeps found places and the weather on an answer', () => {
|
||||
const parts = [
|
||||
{ type: 'data-trek-places', data: { toolCallId: 'toolu_01', places: [], more: 0 } },
|
||||
{ type: 'data-trek-weather', data: { days: [{ ...rainyDay, kind: 'forecast' }] } },
|
||||
];
|
||||
expect(assistantMessageSchema.safeParse({ ...message, role: 'assistant', parts }).success).toBe(true);
|
||||
});
|
||||
|
||||
it('keeps the checked mentions of a question with their labels', () => {
|
||||
const item = { kind: 'place', id: 4, label: 'Louvre' };
|
||||
const parts = [
|
||||
{ type: 'text', text: 'Is @Louvre open on Monday?' },
|
||||
{ type: 'data-trek-mentions', data: { items: [item] } },
|
||||
];
|
||||
expect(assistantMessageSchema.safeParse({ ...message, role: 'user', parts }).success).toBe(true);
|
||||
const tooLong = [{ type: 'data-trek-mentions', data: { items: [{ ...item, label: 'x'.repeat(121) }] } }];
|
||||
expect(assistantMessageSchema.safeParse({ ...message, role: 'user', parts: tooLong }).success).toBe(false);
|
||||
});
|
||||
|
||||
it('never stores the live activity of a step', () => {
|
||||
const activity = { type: 'data-trek-activity', data: { toolCallId: 'toolu_01', label: null, area: null } };
|
||||
expect(assistantMessageSchema.safeParse({ ...message, role: 'assistant', parts: [activity] }).success).toBe(false);
|
||||
});
|
||||
|
||||
it('answers a suggested found place with the message that holds it', () => {
|
||||
const answer = { ...message, role: 'assistant', status: 'awaiting_approval', parts: [] };
|
||||
expect(assistantAddFoundPlaceResponseSchema.safeParse({ message: answer }).success).toBe(true);
|
||||
expect(assistantAddFoundPlaceResponseSchema.safeParse({}).success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('the new stream chunks', () => {
|
||||
const activity = (data: Record<string, unknown>) =>
|
||||
uiStreamChunkSchema.safeParse({ type: 'data-trek-activity', data: { toolCallId: 'toolu_01', ...data } }).success;
|
||||
|
||||
it('streams the live activity of a step, marked transient, with a point or a box', () => {
|
||||
const point = { type: 'point', lat: 41.89, lng: 12.47, radiusM: 1500 };
|
||||
const bbox = { type: 'bbox', south: 41.8, west: 12.4, north: 41.95, east: 12.6 };
|
||||
for (const area of [point, bbox, null]) expect(activity({ label: 'Trastevere', area })).toBe(true);
|
||||
const chunk = { type: 'data-trek-activity', transient: true, data: { toolCallId: 't', label: null, area: null } };
|
||||
expect(uiStreamChunkSchema.safeParse(chunk).success).toBe(true);
|
||||
});
|
||||
|
||||
it('refuses an activity with a radius past 50 km or a label past 80 characters', () => {
|
||||
const area = { type: 'point', lat: 41.89, lng: 12.47, radiusM: 50_001 };
|
||||
expect(activity({ label: null, area })).toBe(false);
|
||||
expect(activity({ label: 'x'.repeat(81), area: null })).toBe(false);
|
||||
});
|
||||
|
||||
it('streams found places and the weather as they are stored', () => {
|
||||
const places = { type: 'data-trek-places', data: { toolCallId: 'toolu_01', places: [], more: 3 } };
|
||||
const day = { dayId: null, date: '2026-06-12', main: 'Clouds', tempMax: 21, tempMin: null, rainPct: null };
|
||||
const weather = { type: 'data-trek-weather', data: { days: [{ ...day, kind: 'current' }] } };
|
||||
expect(uiStreamChunkSchema.safeParse(places).success).toBe(true);
|
||||
expect(uiStreamChunkSchema.safeParse(weather).success).toBe(true);
|
||||
});
|
||||
|
||||
it('names the found-place refusal by its code', () => {
|
||||
expect(uiStreamChunkSchema.parse({ type: 'error', errorText: 'decide_first' })).toEqual({
|
||||
type: 'error',
|
||||
errorText: 'decide_first',
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,507 @@
|
||||
import { idParamSchema, idSchema } from '../common/primitives.schema';
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* Trip assistant contract for /api/trips/:tripId/assistant.
|
||||
*
|
||||
* The assistant works the trip through the same MCP tools any client uses.
|
||||
* Reading tools run at once; every writing call is held as a change preview
|
||||
* the user applies or discards, and only then runs, with the arguments the
|
||||
* server stored, never ones the client sends back. A file attached in the
|
||||
* chat (`attachments`) is read by the server itself: the step is the call
|
||||
* `read_booking_file`, and each booking it finds is held as an
|
||||
* `import_booking` change like any other. Neither is a tool the model knows.
|
||||
*
|
||||
* `chat`, `attachments` and `threads/:threadId/decisions` answer with a stream of UI message
|
||||
* chunks (`uiStreamChunkSchema`, a subset of the UI Message Stream v1 plus the
|
||||
* `data-trek-*` parts), one `data: <json>` event each, closed by
|
||||
* `data: [DONE]`. `__fixtures__/ui-stream.v1.txt` is one such stream; the
|
||||
* server's writer and the client's parser are both tested against it.
|
||||
*/
|
||||
|
||||
/** Why a turn could not run or stopped, as the `error` chunk and the JSON errors name it. */
|
||||
export const ASSISTANT_ERROR_CODES = [
|
||||
'not_connected',
|
||||
'busy',
|
||||
'daily_limit',
|
||||
'rate_limited',
|
||||
'needs_reauth',
|
||||
'usage_limit',
|
||||
'not_eligible',
|
||||
'invalid_key',
|
||||
'provider_unavailable',
|
||||
'max_steps',
|
||||
'offline',
|
||||
'no_bookings_found',
|
||||
'import_unavailable',
|
||||
// A found place was suggested while the thread still holds changes nobody decided on.
|
||||
'decide_first',
|
||||
// TREK's own per-user limit on turns and changes, apart from the provider's `rate_limited`.
|
||||
'too_many_requests',
|
||||
'generic',
|
||||
] as const;
|
||||
export const assistantErrorCodeSchema = z.enum(ASSISTANT_ERROR_CODES);
|
||||
export type AssistantErrorCode = z.infer<typeof assistantErrorCodeSchema>;
|
||||
|
||||
/**
|
||||
* What a change does, one per writing tool. The client labels the card with
|
||||
* `assistant.change.verb.<key>`.
|
||||
*/
|
||||
export const ASSISTANT_CHANGE_VERB_KEYS = [
|
||||
'addPlace',
|
||||
'addPlaceToDay',
|
||||
'updatePlace',
|
||||
'assignPlace',
|
||||
'moveAssignment',
|
||||
'removeFromDay',
|
||||
'addDayNote',
|
||||
'updateDayNote',
|
||||
'addPackingItem',
|
||||
'updatePackingItem',
|
||||
'addTodo',
|
||||
'addBudgetItem',
|
||||
'addReservation',
|
||||
'importBooking',
|
||||
] as const;
|
||||
export const assistantChangeVerbKeySchema = z.enum(ASSISTANT_CHANGE_VERB_KEYS);
|
||||
export type AssistantChangeVerbKey = z.infer<typeof assistantChangeVerbKeySchema>;
|
||||
|
||||
/** The field a change row shows, labelled `assistant.change.label.<key>`. */
|
||||
export const ASSISTANT_CHANGE_LABEL_KEYS = [
|
||||
'name',
|
||||
'description',
|
||||
'address',
|
||||
'coordinates',
|
||||
'category',
|
||||
'day',
|
||||
'time',
|
||||
'endTime',
|
||||
'notes',
|
||||
'website',
|
||||
'phone',
|
||||
'email',
|
||||
'openingHours',
|
||||
'price',
|
||||
'currency',
|
||||
'stopType',
|
||||
'text',
|
||||
'icon',
|
||||
'color',
|
||||
'position',
|
||||
'quantity',
|
||||
'weight',
|
||||
'bag',
|
||||
'packed',
|
||||
'dueDate',
|
||||
'priority',
|
||||
'assignee',
|
||||
'type',
|
||||
'location',
|
||||
'link',
|
||||
'confirmation',
|
||||
'checkIn',
|
||||
'checkOut',
|
||||
'stay',
|
||||
'members',
|
||||
'route',
|
||||
] as const;
|
||||
export const assistantChangeLabelKeySchema = z.enum(ASSISTANT_CHANGE_LABEL_KEYS);
|
||||
export type AssistantChangeLabelKey = z.infer<typeof assistantChangeLabelKeySchema>;
|
||||
|
||||
const latSchema = z.number().min(-90).max(90);
|
||||
const lngSchema = z.number().min(-180).max(180);
|
||||
|
||||
// ── Requests ────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* A day, place or booking the user named with `@` in the message. The server
|
||||
* checks each against the trip and takes the label from its own data, never
|
||||
* from the request; one that is not in the trip is dropped without a word.
|
||||
*/
|
||||
export const ASSISTANT_MENTION_KINDS = ['day', 'place', 'reservation'] as const;
|
||||
export const assistantMentionSchema = z
|
||||
.object({
|
||||
kind: z.enum(ASSISTANT_MENTION_KINDS),
|
||||
id: idSchema,
|
||||
})
|
||||
.strict();
|
||||
export type AssistantMention = z.infer<typeof assistantMentionSchema>;
|
||||
|
||||
/** What the user is looking at when they ask: the selected day, or the places they asked about. */
|
||||
export const assistantFocusSchema = z
|
||||
.object({
|
||||
dayId: idSchema.optional(),
|
||||
placeIds: z.array(idSchema).max(20).optional(),
|
||||
})
|
||||
.strict();
|
||||
export type AssistantFocus = z.infer<typeof assistantFocusSchema>;
|
||||
|
||||
/** The language to answer in and the zone "today" is read in, sent with every turn. */
|
||||
export const assistantClientCtxSchema = z.object({
|
||||
locale: z.string().min(2).max(10),
|
||||
timeZone: z.string().min(1).max(64),
|
||||
});
|
||||
export type AssistantClientCtx = z.infer<typeof assistantClientCtxSchema>;
|
||||
|
||||
/** POST /chat: a new message, in an existing thread or (threadId null) a new one. */
|
||||
export const assistantChatRequestSchema = assistantClientCtxSchema
|
||||
.extend({
|
||||
threadId: idSchema.nullable(),
|
||||
text: z.string().trim().min(1).max(4000),
|
||||
focus: assistantFocusSchema.optional(),
|
||||
mentions: z.array(assistantMentionSchema).max(10).optional(),
|
||||
})
|
||||
.strict();
|
||||
export type AssistantChatRequest = z.infer<typeof assistantChatRequestSchema>;
|
||||
|
||||
/**
|
||||
* POST /attachments, multipart: the file in the field `file`, these as text
|
||||
* fields beside it. A new thread when threadId is left out. Multipart sends
|
||||
* every field as a string, so the id is read from its text.
|
||||
*/
|
||||
export const assistantAttachmentRequestSchema = assistantClientCtxSchema
|
||||
.extend({
|
||||
threadId: idParamSchema.optional(),
|
||||
})
|
||||
.strict();
|
||||
export type AssistantAttachmentRequest = z.infer<typeof assistantAttachmentRequestSchema>;
|
||||
|
||||
/** One decision on a held change. */
|
||||
export const assistantDecisionSchema = z
|
||||
.object({
|
||||
toolCallId: z.string().min(1).max(64),
|
||||
approved: z.boolean(),
|
||||
})
|
||||
.strict();
|
||||
export type AssistantDecision = z.infer<typeof assistantDecisionSchema>;
|
||||
|
||||
/** POST /threads/:threadId/decisions: every change the step holds, applied or discarded. */
|
||||
export const assistantDecisionsRequestSchema = assistantClientCtxSchema
|
||||
.extend({
|
||||
decisions: z.array(assistantDecisionSchema).min(1).max(12),
|
||||
})
|
||||
.strict();
|
||||
export type AssistantDecisionsRequest = z.infer<typeof assistantDecisionsRequestSchema>;
|
||||
|
||||
/** POST /stop. No fields: the body is JSON only so the request cannot be sent cross-site as a form. */
|
||||
export const assistantStopRequestSchema = z.object({}).strict();
|
||||
export type AssistantStopRequest = z.infer<typeof assistantStopRequestSchema>;
|
||||
|
||||
/** POST /tool-calls/:toolCallId/undo. No fields, for the same reason as stop. */
|
||||
export const assistantUndoRequestSchema = z.object({}).strict();
|
||||
export type AssistantUndoRequest = z.infer<typeof assistantUndoRequestSchema>;
|
||||
|
||||
/**
|
||||
* POST /threads/:threadId/found-places: suggest a place from a search result
|
||||
* for a day. `ref` names the place in the thread's stored places part; the
|
||||
* server holds the change like any other and runs no model turn for it.
|
||||
*/
|
||||
export const assistantAddFoundPlaceRequestSchema = assistantClientCtxSchema
|
||||
.extend({
|
||||
ref: z.string().min(1).max(80),
|
||||
dayId: idSchema,
|
||||
})
|
||||
.strict();
|
||||
export type AssistantAddFoundPlaceRequest = z.infer<typeof assistantAddFoundPlaceRequestSchema>;
|
||||
|
||||
// ── Change previews ─────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* What a change does to the day plan, so the map can show it before it is
|
||||
* applied: the day it lands on (for a removal, the day it leaves), the day a
|
||||
* move comes from, and for a place that does not exist yet its name and
|
||||
* position. Null for a change that does not touch the plan.
|
||||
*/
|
||||
export const assistantChangeEffectSchema = z.object({
|
||||
kind: z.enum(['add', 'remove', 'move']),
|
||||
dayId: idSchema,
|
||||
fromDayId: idSchema.nullable(),
|
||||
placeId: idSchema.nullable(),
|
||||
assignmentId: idSchema.nullable(),
|
||||
place: z
|
||||
.object({
|
||||
name: z.string().max(200),
|
||||
lat: latSchema,
|
||||
lng: lngSchema,
|
||||
categoryId: idSchema.nullable(),
|
||||
})
|
||||
.nullable(),
|
||||
});
|
||||
export type AssistantChangeEffect = z.infer<typeof assistantChangeEffectSchema>;
|
||||
|
||||
/** One line of a change card, in plain words: names, dates and times, never ids. */
|
||||
export const assistantChangeRowSchema = z.object({
|
||||
op: z.enum(['add', 'change', 'move']),
|
||||
labelKey: assistantChangeLabelKeySchema,
|
||||
before: z.string().nullable(),
|
||||
after: z.string(),
|
||||
});
|
||||
export type AssistantChangeRow = z.infer<typeof assistantChangeRowSchema>;
|
||||
|
||||
/** A held writing call as the user sees it before deciding. */
|
||||
export const assistantChangePreviewSchema = z.object({
|
||||
toolCallId: z.string(),
|
||||
verbKey: assistantChangeVerbKeySchema,
|
||||
// What the change is about: the place, note or item name.
|
||||
subject: z.string(),
|
||||
rows: z.array(assistantChangeRowSchema).max(12),
|
||||
// Where "Show on map" goes after the change was applied.
|
||||
dayId: idSchema.nullable(),
|
||||
placeIds: z.array(idSchema),
|
||||
// Whether applying it can be undone for a while (a create with a matching delete).
|
||||
undoable: z.boolean(),
|
||||
// Left out by servers and stored previews from before the map showed changes.
|
||||
effect: assistantChangeEffectSchema.nullable().optional(),
|
||||
});
|
||||
export type AssistantChangePreview = z.infer<typeof assistantChangePreviewSchema>;
|
||||
|
||||
/** An applied change: what it touched, and until when it can be undone. */
|
||||
export const assistantAppliedSchema = z.object({
|
||||
toolCallId: z.string(),
|
||||
entity: z.enum(['place', 'assignment', 'day_note', 'packing', 'todo', 'budget', 'reservation']),
|
||||
ids: z.array(idSchema),
|
||||
dayId: idSchema.nullable(),
|
||||
undoUntil: z.string().nullable(),
|
||||
});
|
||||
export type AssistantApplied = z.infer<typeof assistantAppliedSchema>;
|
||||
|
||||
// ── Cards in an answer ──────────────────────────────────────────────────────
|
||||
|
||||
/** The kind a found place is drawn as, read from whatever its source calls it. */
|
||||
export const ASSISTANT_FOUND_PLACE_KINDS = [
|
||||
'lodging',
|
||||
'food',
|
||||
'drinks',
|
||||
'sight',
|
||||
'shopping',
|
||||
'transport',
|
||||
'beach',
|
||||
'nature',
|
||||
'activity',
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* One place a search step found, normalized by the server from the tool's
|
||||
* result. It never carries a URL: the client asks TREK's photo proxy with
|
||||
* `photoKey` (a source id or `coords:lat,lng`) and shows only a same-origin
|
||||
* answer. `openNow` is set only from the source's own flag and only while
|
||||
* the trip is running; `inTripPlaceId` names the trip's place when it is the
|
||||
* same one.
|
||||
*/
|
||||
export const assistantFoundPlaceSchema = z.object({
|
||||
// `${toolCallId}:${index}`, the handle a suggestion for a day names it by.
|
||||
ref: z.string().max(80),
|
||||
name: z.string().max(120),
|
||||
address: z.string().max(200).nullable(),
|
||||
lat: latSchema,
|
||||
lng: lngSchema,
|
||||
kind: z.enum(ASSISTANT_FOUND_PLACE_KINDS).nullable(),
|
||||
rating: z.number().min(0).max(5).nullable(),
|
||||
openNow: z.boolean().nullable(),
|
||||
googlePlaceId: z.string().regex(/^[\w-]{1,200}$/).nullable(),
|
||||
osmId: z.string().max(40).nullable(),
|
||||
photoKey: z.string().regex(/^([\w-]{1,200}|coords:-?\d{1,3}(\.\d{1,12})?,-?\d{1,3}(\.\d{1,12})?)$/).nullable(),
|
||||
inTripPlaceId: idSchema.nullable(),
|
||||
});
|
||||
export type AssistantFoundPlace = z.infer<typeof assistantFoundPlaceSchema>;
|
||||
|
||||
/** The places one search step found: at most eight, `more` counts the rest. */
|
||||
export const assistantPlacesSchema = z.object({
|
||||
toolCallId: z.string().max(64),
|
||||
places: z.array(assistantFoundPlaceSchema).max(8),
|
||||
more: z.number().int().nonnegative(),
|
||||
});
|
||||
export type AssistantPlaces = z.infer<typeof assistantPlacesSchema>;
|
||||
|
||||
/**
|
||||
* The weather the steps of an answer looked up, one entry per date.
|
||||
* Temperatures are °C; the client converts them to the user's unit. `main`
|
||||
* is the condition the weather icon is chosen by.
|
||||
*/
|
||||
export const assistantWeatherDaySchema = z.object({
|
||||
dayId: idSchema.nullable(),
|
||||
date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
|
||||
main: z.string().max(20),
|
||||
tempMax: z.number(),
|
||||
tempMin: z.number().nullable(),
|
||||
rainPct: z.number().int().min(0).max(100).nullable(),
|
||||
kind: z.enum(['forecast', 'climate', 'current']),
|
||||
});
|
||||
export type AssistantWeatherDay = z.infer<typeof assistantWeatherDaySchema>;
|
||||
|
||||
export const assistantWeatherSchema = z.object({
|
||||
days: z.array(assistantWeatherDaySchema).min(1).max(16),
|
||||
});
|
||||
export type AssistantWeather = z.infer<typeof assistantWeatherSchema>;
|
||||
|
||||
/** Where a running step looks: a point with a radius, or a box. */
|
||||
export const assistantActivityAreaSchema = z.discriminatedUnion('type', [
|
||||
z.object({
|
||||
type: z.literal('point'),
|
||||
lat: latSchema,
|
||||
lng: lngSchema,
|
||||
radiusM: z.number().int().min(50).max(50_000),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal('bbox'),
|
||||
south: latSchema,
|
||||
west: lngSchema,
|
||||
north: latSchema,
|
||||
east: lngSchema,
|
||||
}),
|
||||
]);
|
||||
export type AssistantActivityArea = z.infer<typeof assistantActivityAreaSchema>;
|
||||
|
||||
/**
|
||||
* What a running step is doing, for the live line and the map: the place or
|
||||
* query it names (shortened), and the area it searches when that is small
|
||||
* enough to show. Only streamed, never stored.
|
||||
*/
|
||||
export const assistantActivitySchema = z.object({
|
||||
toolCallId: z.string().max(64),
|
||||
label: z.string().max(80).nullable(),
|
||||
area: assistantActivityAreaSchema.nullable(),
|
||||
});
|
||||
export type AssistantActivity = z.infer<typeof assistantActivitySchema>;
|
||||
|
||||
/** The mentions of a user message as the server checked them, with the labels it took from the trip. */
|
||||
export const assistantMentionsSchema = z.object({
|
||||
items: z
|
||||
.array(
|
||||
z.object({
|
||||
kind: z.enum(ASSISTANT_MENTION_KINDS),
|
||||
id: idSchema,
|
||||
label: z.string().max(120),
|
||||
}),
|
||||
)
|
||||
.max(10),
|
||||
});
|
||||
export type AssistantMentions = z.infer<typeof assistantMentionsSchema>;
|
||||
|
||||
// ── Threads and messages ────────────────────────────────────────────────────
|
||||
|
||||
export const assistantThreadSummarySchema = z.object({
|
||||
id: idSchema,
|
||||
title: z.string(),
|
||||
lastMessageAt: z.string(),
|
||||
});
|
||||
export type AssistantThreadSummary = z.infer<typeof assistantThreadSummarySchema>;
|
||||
|
||||
/** GET /threads: the user's latest threads in this trip. */
|
||||
export const assistantThreadListResponseSchema = z.object({ threads: z.array(assistantThreadSummarySchema) });
|
||||
export type AssistantThreadListResponse = z.infer<typeof assistantThreadListResponseSchema>;
|
||||
|
||||
/**
|
||||
* A server-side approval gate on a tool call. `resolution` closes a request
|
||||
* nobody decided: it expired after 24 hours, or the turn was cancelled.
|
||||
*/
|
||||
export const assistantToolApprovalSchema = z.object({
|
||||
id: z.string(),
|
||||
approved: z.boolean().optional(),
|
||||
resolution: z.enum(['cancelled', 'expired']).optional(),
|
||||
});
|
||||
export type AssistantToolApproval = z.infer<typeof assistantToolApprovalSchema>;
|
||||
|
||||
/** One stored part of a message, in the order the model produced it. */
|
||||
export const assistantMessagePartSchema = z.discriminatedUnion('type', [
|
||||
z.object({ type: z.literal('text'), text: z.string() }),
|
||||
// A file the user handed the assistant. Only its description is kept, never its content.
|
||||
z.object({
|
||||
type: z.literal('attachment'),
|
||||
name: z.string(),
|
||||
mime: z.string(),
|
||||
size: z.number().int().nonnegative(),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal('tool-call'),
|
||||
toolCallId: z.string(),
|
||||
toolName: z.string(),
|
||||
args: z.record(z.string(), z.unknown()),
|
||||
result: z.unknown().optional(),
|
||||
isError: z.boolean().optional(),
|
||||
approval: assistantToolApprovalSchema.optional(),
|
||||
}),
|
||||
z.object({ type: z.literal('data-trek-change'), data: assistantChangePreviewSchema }),
|
||||
z.object({ type: z.literal('data-trek-applied'), data: assistantAppliedSchema }),
|
||||
z.object({ type: z.literal('data-trek-places'), data: assistantPlacesSchema }),
|
||||
z.object({ type: z.literal('data-trek-weather'), data: assistantWeatherSchema }),
|
||||
// Only on a user message.
|
||||
z.object({ type: z.literal('data-trek-mentions'), data: assistantMentionsSchema }),
|
||||
]);
|
||||
export type AssistantMessagePart = z.infer<typeof assistantMessagePartSchema>;
|
||||
|
||||
export const assistantMessageSchema = z.object({
|
||||
id: z.string(),
|
||||
role: z.enum(['user', 'assistant']),
|
||||
parts: z.array(assistantMessagePartSchema),
|
||||
status: z.enum(['complete', 'awaiting_approval', 'aborted', 'error']),
|
||||
createdAt: z.string(),
|
||||
});
|
||||
export type AssistantMessage = z.infer<typeof assistantMessageSchema>;
|
||||
|
||||
/** GET /threads/:threadId: the thread with its latest 50 messages, oldest first. */
|
||||
export const assistantThreadResponseSchema = z.object({
|
||||
thread: assistantThreadSummarySchema,
|
||||
messages: z.array(assistantMessageSchema),
|
||||
});
|
||||
export type AssistantThreadResponse = z.infer<typeof assistantThreadResponseSchema>;
|
||||
|
||||
/** POST /threads/:threadId/found-places: the new assistant message holding the suggested change. */
|
||||
export const assistantAddFoundPlaceResponseSchema = z.object({ message: assistantMessageSchema });
|
||||
export type AssistantAddFoundPlaceResponse = z.infer<typeof assistantAddFoundPlaceResponseSchema>;
|
||||
|
||||
/** POST /tool-calls/:toolCallId/undo */
|
||||
export const assistantUndoResponseSchema = z.object({ undone: z.literal(true) });
|
||||
export type AssistantUndoResponse = z.infer<typeof assistantUndoResponseSchema>;
|
||||
|
||||
// ── Stream ──────────────────────────────────────────────────────────────────
|
||||
|
||||
/** The marker the last `data:` event of a stream carries instead of a chunk. */
|
||||
export const UI_STREAM_DONE = '[DONE]';
|
||||
|
||||
/**
|
||||
* The chunks of an assistant stream. A client ignores a chunk whose type it
|
||||
* does not know, so new types can be added without breaking older clients.
|
||||
* An error names an `AssistantErrorCode`; one this client does not know reads
|
||||
* as `generic`.
|
||||
*/
|
||||
export const uiStreamChunkSchema = z.discriminatedUnion('type', [
|
||||
z.object({ type: z.literal('start'), messageId: z.string() }),
|
||||
z.object({ type: z.literal('start-step') }),
|
||||
z.object({ type: z.literal('text-start'), id: z.string() }),
|
||||
z.object({ type: z.literal('text-delta'), id: z.string(), delta: z.string() }),
|
||||
z.object({ type: z.literal('text-end'), id: z.string() }),
|
||||
z.object({ type: z.literal('tool-input-start'), toolCallId: z.string(), toolName: z.string() }),
|
||||
z.object({
|
||||
type: z.literal('tool-input-available'),
|
||||
toolCallId: z.string(),
|
||||
toolName: z.string(),
|
||||
input: z.record(z.string(), z.unknown()),
|
||||
}),
|
||||
// approvalId is the toolCallId: one id addresses the call and its decision.
|
||||
z.object({ type: z.literal('tool-approval-request'), approvalId: z.string(), toolCallId: z.string() }),
|
||||
z.object({ type: z.literal('tool-output-available'), toolCallId: z.string(), output: z.unknown() }),
|
||||
z.object({ type: z.literal('tool-output-error'), toolCallId: z.string(), errorText: z.string() }),
|
||||
z.object({ type: z.literal('tool-output-denied'), toolCallId: z.string() }),
|
||||
z.object({ type: z.literal('data-trek-thread'), data: z.object({ threadId: idSchema, title: z.string() }) }),
|
||||
z.object({ type: z.literal('data-trek-change'), data: assistantChangePreviewSchema }),
|
||||
z.object({ type: z.literal('data-trek-applied'), data: assistantAppliedSchema }),
|
||||
z.object({ type: z.literal('data-trek-places'), data: assistantPlacesSchema }),
|
||||
z.object({ type: z.literal('data-trek-weather'), data: assistantWeatherSchema }),
|
||||
// Live progress of a running step; the message never keeps it.
|
||||
z.object({
|
||||
type: z.literal('data-trek-activity'),
|
||||
data: assistantActivitySchema,
|
||||
transient: z.literal(true).optional(),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal('data-trek-quota'),
|
||||
data: z.object({ used: z.number().int().nonnegative(), limit: z.number().int().nonnegative() }),
|
||||
}),
|
||||
z.object({ type: z.literal('finish-step') }),
|
||||
z.object({ type: z.literal('finish'), finishReason: z.enum(['stop', 'tool-calls', 'error']).optional() }),
|
||||
z.object({ type: z.literal('error'), errorText: assistantErrorCodeSchema.catch('generic') }),
|
||||
z.object({ type: z.literal('abort') }),
|
||||
]);
|
||||
export type UiStreamChunk = z.infer<typeof uiStreamChunkSchema>;
|
||||
@@ -7,6 +7,8 @@ import {
|
||||
collabLinkUpdateRequestSchema,
|
||||
collabMessageCreateRequestSchema,
|
||||
collabReactionRequestSchema,
|
||||
collabMessageSchema,
|
||||
COLLAB_MESSAGE_ORIGIN_ASSISTANT,
|
||||
} from './collab.schema';
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
@@ -106,3 +108,34 @@ describe('collabReactionRequestSchema', () => {
|
||||
expect(collabReactionRequestSchema.safeParse({ emoji: '' }).success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('collabMessageSchema', () => {
|
||||
const message = {
|
||||
id: 1,
|
||||
trip_id: 2,
|
||||
user_id: 3,
|
||||
text: 'See you at the station',
|
||||
reply_to: null,
|
||||
created_at: '2026-10-21 08:00:00',
|
||||
deleted: null,
|
||||
username: 'ana',
|
||||
avatar: null,
|
||||
reply_text: null,
|
||||
reply_username: null,
|
||||
user_avatar: null,
|
||||
avatar_url: null,
|
||||
reactions: [],
|
||||
attachments: [],
|
||||
};
|
||||
|
||||
it('reads a message without an origin, with none and with one shared from the assistant', () => {
|
||||
expect(collabMessageSchema.safeParse(message).success).toBe(true);
|
||||
expect(collabMessageSchema.safeParse({ ...message, origin: null }).success).toBe(true);
|
||||
expect(collabMessageSchema.parse({ ...message, origin: COLLAB_MESSAGE_ORIGIN_ASSISTANT }).origin).toBe('assistant');
|
||||
});
|
||||
|
||||
it('takes an origin it does not know yet, but only as text', () => {
|
||||
expect(collabMessageSchema.safeParse({ ...message, origin: 'plugin' }).success).toBe(true);
|
||||
expect(collabMessageSchema.safeParse({ ...message, origin: 1 }).success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -202,9 +202,15 @@ export const collabMessageSchema = z.object({
|
||||
avatar_url: z.string().nullable(),
|
||||
reactions: z.array(collabReactionSchema),
|
||||
attachments: z.array(collabAttachmentSchema),
|
||||
// Where the text came from when no one typed it. A string rather than an
|
||||
// enum, so a value added later does not break an older parser.
|
||||
origin: z.string().nullable().optional(),
|
||||
});
|
||||
export type CollabMessage = z.infer<typeof collabMessageSchema>;
|
||||
|
||||
/** The `origin` of a message shared from an answer of the trip assistant. */
|
||||
export const COLLAB_MESSAGE_ORIGIN_ASSISTANT = 'assistant';
|
||||
|
||||
export const collabMessagesResponseSchema = z.object({ messages: z.array(collabMessageSchema) });
|
||||
export type CollabMessagesResponse = z.infer<typeof collabMessagesResponseSchema>;
|
||||
export const collabMessageResponseSchema = z.object({ message: collabMessageSchema });
|
||||
|
||||
@@ -687,6 +687,8 @@ const admin: TranslationStrings = {
|
||||
'admin.addons.catalog.llm_parsing.name': 'التحليل بالذكاء الاصطناعي',
|
||||
'admin.addons.catalog.llm_parsing.description':
|
||||
'يقرأ الحجوزات التي يعجز المحلل المدمج عن فهمها، بنموذج ذكاء اصطناعي تختاره',
|
||||
'admin.addons.catalog.ai_assistant.name': 'المساعد الذكي',
|
||||
'admin.addons.catalog.ai_assistant.description': 'محادثة في كل رحلة تجيب عن الأسئلة وتقترح تغييرات تؤكدها أنت',
|
||||
'admin.addons.llm.vision.auto': 'تلقائي',
|
||||
'admin.addons.llm.vision.on': 'نعم',
|
||||
'admin.addons.llm.vision.off': 'لا',
|
||||
|
||||
@@ -0,0 +1,421 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistant: TranslationStrings = {
|
||||
'assistant.launcher.tooltip': 'المساعد',
|
||||
'assistant.launcher.ctrlKey': 'Ctrl',
|
||||
'assistant.picker.title': 'أي رحلة؟',
|
||||
'assistant.picker.empty': 'أنشئ رحلة أولًا، وبعدها يساعدك المساعد في تخطيطها.',
|
||||
'assistant.picker.now': 'الآن',
|
||||
'assistant.picker.next': 'التالية',
|
||||
'assistant.window.title': 'المساعد',
|
||||
'assistant.window.log': 'المحادثة مع المساعد',
|
||||
'assistant.window.menu': 'خيارات أخرى',
|
||||
'assistant.window.settings': 'إعدادات الذكاء الاصطناعي',
|
||||
'assistant.window.deleteThread': 'حذف المحادثة',
|
||||
'assistant.window.deleteConfirm': 'حذف هذه المحادثة؟ لا يمكن التراجع عن ذلك.',
|
||||
'assistant.setup.question': 'بماذا تريد أن تدردش؟',
|
||||
'assistant.setup.beta': 'تجريبي',
|
||||
'assistant.setup.chatgptNote': 'مفتاح API هو الطريقة الرسمية. أما تسجيل الدخول عبر ChatGPT فما زال تجريبيًا.',
|
||||
'assistant.setup.keySubtitle': 'Anthropic أو OpenAI أو Gemini أو OpenRouter',
|
||||
'assistant.setup.adminOnly': 'يتولى المسؤول هذا الإعداد.',
|
||||
'assistant.setup.forEveryone': 'إعداد للجميع',
|
||||
'assistant.connect.title': 'المساعد الذكي',
|
||||
'assistant.connect.description': 'تحدث مع مساعد يعرف رحلتك ويقترح تغييرات تؤكدها أنت.',
|
||||
'assistant.connect.chatgpt': 'تسجيل الدخول عبر ChatGPT',
|
||||
'assistant.connect.chatgptHint': 'يستخدم اشتراكك في ChatGPT. لا حاجة إلى مفتاح.',
|
||||
'assistant.connect.chatgptStep1': 'سجّل الدخول إلى ChatGPT في علامة التبويب الجديدة.',
|
||||
'assistant.connect.chatgptOpen': 'فتح تسجيل الدخول إلى ChatGPT',
|
||||
'assistant.connect.chatgptStep2': 'سيعرض المتصفح بعدها صفحة خطأ، وهذا متوقع. انسخ العنوان من الأعلى ثم عد إلى هنا.',
|
||||
'assistant.connect.chatgptPaste': 'لصق العنوان',
|
||||
'assistant.connect.chatgptWrong': 'هذا ليس العنوان الصحيح. يبدأ العنوان بـ 127.0.0.1.',
|
||||
'assistant.connect.chatgptExpired': 'استغرق ذلك وقتًا طويلًا. يرجى تسجيل الدخول مرة أخرى.',
|
||||
'assistant.connect.chatgptOtherAccount': 'هذا حساب ChatGPT آخر. أزل الاتصال أولًا.',
|
||||
'assistant.connect.key': 'لصق مفتاح API',
|
||||
'assistant.connect.keyPlaceholder': 'الصق مفتاحك هنا',
|
||||
'assistant.connect.detected': 'تم التعرف على: {provider}',
|
||||
'assistant.connect.whichProvider': 'من أي مزود هذا المفتاح؟',
|
||||
'assistant.connect.testing': 'جارٍ التحقق من الاتصال',
|
||||
'assistant.connect.ok': 'تم الاتصال. لنبدأ.',
|
||||
'assistant.connect.openAssistant': 'فتح المساعد',
|
||||
'assistant.connect.badKey': 'هذا المفتاح لا يعمل.',
|
||||
'assistant.connect.unknownModel': 'هذا النموذج غير متاح هنا.',
|
||||
'assistant.connect.getKey': 'الحصول على مفتاح',
|
||||
'assistant.connect.hintAnthropic': 'تتضمن باقتا Claude Max وTeam رصيدًا لـ API.',
|
||||
'assistant.connect.hintOpenai': 'أنشئ مفتاحًا في لوحة تحكم OpenAI.',
|
||||
'assistant.connect.hintGemini': 'يوفر AI Studio خطة مجانية بحدود في الدقيقة واليوم، وقد تستخدم Google فيها ما ترسله لتحسين منتجاتها. عند تفعيل الفوترة تكون الحدود أعلى ولا ينطبق ذلك.',
|
||||
'assistant.connect.hintOpenrouter': 'سجّل الدخول عبر OpenRouter، دون مفتاح لنسخه.',
|
||||
'assistant.connect.openrouter': 'ربط OpenRouter',
|
||||
'assistant.connect.save': 'اتصال',
|
||||
'assistant.connect.model': 'النموذج',
|
||||
'assistant.connect.modelHint': 'تظهر فقط النماذج القادرة على استخدام الأدوات.',
|
||||
'assistant.connect.free': 'تتوفر خطة مجانية',
|
||||
'assistant.connect.paid': 'الدفع حسب الاستخدام',
|
||||
'assistant.model.recommended': 'موصى به',
|
||||
'assistant.model.free': 'مجاني',
|
||||
'assistant.connect.connected': 'متصل بـ {provider}',
|
||||
'assistant.connect.providedByAdmin': 'مقدَّم من المسؤول',
|
||||
'assistant.connect.disconnect': 'قطع الاتصال',
|
||||
'assistant.connect.disconnectConfirm': 'قطع الاتصال بـ {provider}؟ تبقى محادثاتك محفوظة.',
|
||||
'assistant.connect.signOut': 'تسجيل الخروج',
|
||||
'assistant.connect.statusOk': 'متصل',
|
||||
'assistant.connect.statusNeedsReauth': 'انتهت صلاحية تسجيل الدخول',
|
||||
'assistant.connect.statusSignedOut': 'تم تسجيل الخروج',
|
||||
'assistant.connect.statusError': 'غير متاح',
|
||||
'assistant.connect.quota': 'استُخدمت اليوم {used} من {count} رسالة',
|
||||
'assistant.connect.quota.zero': 'استُخدمت اليوم {used} من {count} رسالة',
|
||||
'assistant.connect.quota.one': 'استُخدمت اليوم {used} من رسالة واحدة',
|
||||
'assistant.connect.quota.two': 'استُخدمت اليوم {used} من رسالتين',
|
||||
'assistant.connect.quota.few': 'استُخدمت اليوم {used} من {count} رسائل',
|
||||
'assistant.connect.quota.many': 'استُخدمت اليوم {used} من {count} رسالة',
|
||||
'assistant.connect.claudeLink': 'تستخدم Claude؟ اربطه من داخل Claude',
|
||||
'assistant.privacy.what': 'ما الذي تتم مشاركته؟',
|
||||
'assistant.privacy.banner': 'تُرسل رسائلك وبيانات الرحلة التي يقرؤها المساعد إلى {provider}.',
|
||||
'assistant.privacy.ok': 'فهمت',
|
||||
'assistant.privacy.shared': 'رسائلك وتفاصيل الرحلة التي يقرؤها المساعد، مثل الأماكن والأيام والحجوزات.',
|
||||
'assistant.privacy.notShared': 'لا كلمة مرورك ولا ملفاتك ولا رحلاتك الأخرى.',
|
||||
'assistant.privacy.provider': 'يعالج {provider} هذه البيانات وفق شروط الخصوصية الخاصة به.',
|
||||
'assistant.empty.title': 'ماذا نخطط؟',
|
||||
'assistant.empty.readOnly': 'يمكنك عرض هذه الرحلة فقط، لذا أستطيع الإجابة عن الأسئلة لكن لا أغيّر شيئًا.',
|
||||
'assistant.suggest.planFirstDays': 'خطط لي الأيام الأولى',
|
||||
'assistant.suggest.mustSee': 'ما الذي لا ينبغي أن يفوتني هنا؟',
|
||||
'assistant.suggest.packingList': 'أعد قائمة أمتعة لهذه الرحلة',
|
||||
'assistant.suggest.emptyDays': 'أي الأيام ما زالت فارغة؟',
|
||||
'assistant.suggest.rainPlan': 'ماذا يمكننا أن نفعل إذا أمطرت؟',
|
||||
'assistant.suggest.today': 'ما برنامج اليوم؟',
|
||||
'assistant.suggest.eatNearby': 'اعثر على مكان قريب للأكل',
|
||||
'assistant.suggest.weather': 'كيف سيكون الطقس؟',
|
||||
'assistant.suggest.openTodos': 'ما الذي لم يُنجز بعد في قائمة المهام؟',
|
||||
'assistant.suggest.settleUp': 'من يدين لمن؟',
|
||||
'assistant.suggest.recap': 'لخّص رحلتنا',
|
||||
'assistant.suggest.nearPlace': 'ما الذي يوجد أيضًا بالقرب من {place}؟',
|
||||
'assistant.suggest.placeTips': 'نصائح لزيارة {place}',
|
||||
'assistant.suggest.planDay': 'خطط لي {day}',
|
||||
'assistant.suggest.dayTooFull': 'هل {day} مزدحم أكثر من اللازم؟',
|
||||
'assistant.thread.scrollDown': 'الانتقال إلى آخر رسالة',
|
||||
'assistant.attach.button': 'إرفاق حجز',
|
||||
'assistant.attach.drop': 'أفلته هنا وسأقرأ الحجز',
|
||||
'assistant.attach.unsupported': 'لا يمكن قراءة هذا النوع من الملفات. جرّب PDF أو بريدًا أو صورة أو بطاقة محفظة.',
|
||||
'assistant.attach.tooLarge': 'هذا الملف أكبر من 10 ميغابايت.',
|
||||
'assistant.composer.placeholder': 'اسأل أي شيء عن {trip}',
|
||||
'assistant.composer.send': 'إرسال',
|
||||
'assistant.composer.stop': 'إيقاف',
|
||||
'assistant.composer.decideFirst': 'طبّق التغييرات أعلاه أو تجاهلها أولًا.',
|
||||
'assistant.context.ask': 'اسأل',
|
||||
'assistant.context.remove': 'إزالة السياق',
|
||||
'assistant.step.reading': 'قراءة خطتك',
|
||||
'assistant.step.searching': 'البحث عن أماكن',
|
||||
'assistant.step.details': 'البحث عن التفاصيل',
|
||||
'assistant.step.nearby': 'البحث في الجوار',
|
||||
'assistant.step.categories': 'التحقق من الفئات',
|
||||
'assistant.step.weather': 'التحقق من الطقس',
|
||||
'assistant.step.todos': 'قراءة قائمة المهام',
|
||||
'assistant.step.costs': 'التحقق من التكاليف',
|
||||
'assistant.step.help': 'البحث في المساعدة',
|
||||
'assistant.step.readingBooking': 'قراءة الحجز',
|
||||
'assistant.step.working': 'جارٍ العمل على ذلك',
|
||||
'assistant.step.collapsed': '{count} خطوة',
|
||||
'assistant.step.collapsed.zero': 'لا خطوات',
|
||||
'assistant.step.collapsed.one': 'خطوة واحدة',
|
||||
'assistant.step.collapsed.two': 'خطوتان',
|
||||
'assistant.step.collapsed.few': '{count} خطوات',
|
||||
'assistant.step.collapsed.many': '{count} خطوة',
|
||||
'assistant.change.title': 'تغييرات مقترحة',
|
||||
'assistant.change.doneTitle': 'تم',
|
||||
'assistant.change.apply': 'تطبيق',
|
||||
'assistant.change.applyN': 'تطبيق {count}',
|
||||
'assistant.change.applyN.zero': 'تطبيق {count}',
|
||||
'assistant.change.applyN.one': 'تطبيق {count}',
|
||||
'assistant.change.applyN.two': 'تطبيق {count}',
|
||||
'assistant.change.applyN.few': 'تطبيق {count}',
|
||||
'assistant.change.applyN.many': 'تطبيق {count}',
|
||||
'assistant.change.discard': 'تجاهل',
|
||||
'assistant.change.include': 'تضمين هذا التغيير',
|
||||
'assistant.change.applied': 'تم التطبيق',
|
||||
'assistant.change.discarded': 'تم التجاهل',
|
||||
'assistant.change.failed': 'تعذر تطبيق هذا.',
|
||||
'assistant.change.applying': 'جارٍ التطبيق',
|
||||
'assistant.change.expired': 'لم يعد متاحًا',
|
||||
'assistant.change.undo': 'تراجع',
|
||||
'assistant.change.undone': 'تم التراجع',
|
||||
'assistant.change.undoStale': 'تغيّر في هذه الأثناء. يرجى تعديله يدويًا.',
|
||||
'assistant.change.undoFailed': 'تعذر التراجع عن هذا.',
|
||||
'assistant.change.showOnMap': 'عرض على الخريطة',
|
||||
'assistant.change.empty': 'فارغ',
|
||||
'assistant.change.pending': '{count} تغيير بانتظارك',
|
||||
'assistant.change.pending.zero': 'لا تغييرات بانتظارك',
|
||||
'assistant.change.pending.one': 'تغيير واحد بانتظارك',
|
||||
'assistant.change.pending.two': 'تغييران بانتظارك',
|
||||
'assistant.change.pending.few': '{count} تغييرات بانتظارك',
|
||||
'assistant.change.pending.many': '{count} تغييرًا بانتظارك',
|
||||
'assistant.change.verb.addPlace': 'إضافة مكان',
|
||||
'assistant.change.verb.addPlaceToDay': 'إضافة مكان إلى اليوم',
|
||||
'assistant.change.verb.updatePlace': 'تعديل المكان',
|
||||
'assistant.change.verb.assignPlace': 'جدولة المكان في يوم',
|
||||
'assistant.change.verb.moveAssignment': 'نقل إلى يوم آخر',
|
||||
'assistant.change.verb.removeFromDay': 'إزالة من اليوم',
|
||||
'assistant.change.verb.addDayNote': 'إضافة ملاحظة',
|
||||
'assistant.change.verb.updateDayNote': 'تعديل الملاحظة',
|
||||
'assistant.change.verb.addPackingItem': 'إضافة إلى قائمة الأمتعة',
|
||||
'assistant.change.verb.updatePackingItem': 'تعديل غرض في الأمتعة',
|
||||
'assistant.change.verb.addTodo': 'إضافة مهمة',
|
||||
'assistant.change.verb.addBudgetItem': 'إضافة مصروف',
|
||||
'assistant.change.verb.addReservation': 'إضافة حجز',
|
||||
'assistant.change.verb.importBooking': 'إضافة حجز من ملف',
|
||||
'assistant.change.label.name': 'الاسم',
|
||||
'assistant.change.label.description': 'الوصف',
|
||||
'assistant.change.label.address': 'العنوان',
|
||||
'assistant.change.label.coordinates': 'الموقع على الخريطة',
|
||||
'assistant.change.label.category': 'الفئة',
|
||||
'assistant.change.label.day': 'اليوم',
|
||||
'assistant.change.label.time': 'الوقت',
|
||||
'assistant.change.label.endTime': 'حتى',
|
||||
'assistant.change.label.notes': 'ملاحظات',
|
||||
'assistant.change.label.website': 'الموقع الإلكتروني',
|
||||
'assistant.change.label.phone': 'الهاتف',
|
||||
'assistant.change.label.email': 'البريد الإلكتروني',
|
||||
'assistant.change.label.openingHours': 'ساعات العمل',
|
||||
'assistant.change.label.price': 'السعر',
|
||||
'assistant.change.label.currency': 'العملة',
|
||||
'assistant.change.label.stopType': 'نوع التوقف',
|
||||
'assistant.change.label.text': 'النص',
|
||||
'assistant.change.label.icon': 'الأيقونة',
|
||||
'assistant.change.label.color': 'اللون',
|
||||
'assistant.change.label.position': 'الترتيب',
|
||||
'assistant.change.label.quantity': 'الكمية',
|
||||
'assistant.change.label.weight': 'الوزن',
|
||||
'assistant.change.label.bag': 'الحقيبة',
|
||||
'assistant.change.label.packed': 'جُهّز',
|
||||
'assistant.change.label.dueDate': 'موعد الاستحقاق',
|
||||
'assistant.change.label.priority': 'الأولوية',
|
||||
'assistant.change.label.assignee': 'مسند إلى',
|
||||
'assistant.change.label.type': 'النوع',
|
||||
'assistant.change.label.location': 'الموقع',
|
||||
'assistant.change.label.link': 'الرابط',
|
||||
'assistant.change.label.confirmation': 'رقم التأكيد',
|
||||
'assistant.change.label.checkIn': 'تسجيل الوصول',
|
||||
'assistant.change.label.checkOut': 'تسجيل المغادرة',
|
||||
'assistant.change.label.stay': 'الإقامة',
|
||||
'assistant.change.label.members': 'مقسّم بين',
|
||||
'assistant.change.label.route': 'المسار',
|
||||
'assistant.history.open': 'المحادثات الأخيرة',
|
||||
'assistant.history.new': 'محادثة جديدة',
|
||||
'assistant.history.empty': 'لا محادثات بعد.',
|
||||
'assistant.history.retention': 'تُحذف المحادثات بعد {count} يوم.',
|
||||
'assistant.history.retention.zero': 'تُحذف المحادثات بعد {count} يوم.',
|
||||
'assistant.history.retention.one': 'تُحذف المحادثات بعد يوم واحد.',
|
||||
'assistant.history.retention.two': 'تُحذف المحادثات بعد يومين.',
|
||||
'assistant.history.retention.few': 'تُحذف المحادثات بعد {count} أيام.',
|
||||
'assistant.history.retention.many': 'تُحذف المحادثات بعد {count} يومًا.',
|
||||
'assistant.history.forever': 'تُحفظ المحادثات حتى تحذفها.',
|
||||
'assistant.error.notConnected': 'المساعد غير متصل بعد.',
|
||||
'assistant.error.busy': 'يعمل بالفعل في نافذة أخرى.',
|
||||
'assistant.error.dailyLimit': 'هذا كل شيء لليوم. نلتقي غدًا.',
|
||||
'assistant.error.rateLimited': 'تمهل قليلًا. حاول مرة أخرى بعد دقيقة.',
|
||||
'assistant.error.needsReauth': 'انتهت صلاحية تسجيل دخولك إلى {provider}.',
|
||||
'assistant.error.usageLimit': 'استنفدت حصة استخدامك في ChatGPT.',
|
||||
'assistant.error.notEligible': 'لا يمكن استخدام حساب ChatGPT هذا هنا.',
|
||||
'assistant.error.invalidKey': 'مفتاح {provider} الخاص بك لم يعد يعمل.',
|
||||
'assistant.error.providerDown': '{provider} لا يستجيب حاليًا.',
|
||||
'assistant.error.maxSteps': 'كان ذلك كثيرًا دفعة واحدة. هل أكمل؟',
|
||||
'assistant.error.offline': 'غير متصل. يحتاج المساعد إلى الإنترنت.',
|
||||
'assistant.error.noBookingsFound': 'لم يُعثر على أي حجز في هذا الملف.',
|
||||
'assistant.error.importUnavailable': 'قراءة ملفات الحجز غير متاحة هنا.',
|
||||
'assistant.error.generic': 'حدث خطأ ما.',
|
||||
'assistant.errorAction.retry': 'إعادة المحاولة',
|
||||
'assistant.errorAction.continueHere': 'المتابعة هنا',
|
||||
'assistant.errorAction.continue': 'متابعة',
|
||||
'assistant.errorAction.signIn': 'تسجيل الدخول مجددًا',
|
||||
'assistant.errorAction.viewUsage': 'عرض الاستخدام',
|
||||
'assistant.errorAction.otherProvider': 'اختيار مزود آخر',
|
||||
'assistant.errorAction.replaceKey': 'استبدال المفتاح',
|
||||
'assistant.errorAction.connectOwn': 'ربط مزودك الخاص',
|
||||
'assistant.admin.policy': 'من يدفع',
|
||||
'assistant.admin.policy.instance': 'أدفع عن الجميع',
|
||||
'assistant.admin.policy.instanceOrUser': 'أدفع أنا، والحساب الخاص مسموح',
|
||||
'assistant.admin.policy.user': 'كلٌّ يستخدم حسابه الخاص',
|
||||
'assistant.admin.policy.instanceHint': 'يستخدم الجميع الاتصال أدناه ولا يمكنهم إضافة اتصالهم الخاص.',
|
||||
'assistant.admin.policy.instanceOrUserHint': 'يستخدم الجميع الاتصال أدناه ما لم يربطوا اتصالهم الخاص.',
|
||||
'assistant.admin.policy.userHint': 'يربط كل شخص مزوده الخاص. لا يُحتسب عليك أي شيء.',
|
||||
'assistant.admin.connection': 'اتصال للجميع',
|
||||
'assistant.admin.connectionNone': 'لا يوجد اتصال بعد.',
|
||||
'assistant.admin.connectionRemove': 'إزالة الاتصال',
|
||||
'assistant.admin.custom': 'خادم خاص',
|
||||
'assistant.admin.customHint': 'أي خادم متوافق مع OpenAI، مثل Ollama.',
|
||||
'assistant.admin.baseUrl': 'عنوان الخادم',
|
||||
'assistant.admin.apiKeyOptional': 'مفتاح API (اختياري)',
|
||||
'assistant.admin.prefilled': 'مأخوذ من التحليل بالذكاء الاصطناعي.',
|
||||
'assistant.admin.dailyLimit': 'الرسائل لكل شخص يوميًا',
|
||||
'assistant.admin.dailyLimitHint': 'يُحتسب فقط ما دمت أنت من يدفع.',
|
||||
'assistant.admin.retention': 'الاحتفاظ بالمحادثات',
|
||||
'assistant.admin.retentionDays': '{count} يوم',
|
||||
'assistant.admin.retentionDays.zero': '{count} يوم',
|
||||
'assistant.admin.retentionDays.one': 'يوم واحد',
|
||||
'assistant.admin.retentionDays.two': 'يومان',
|
||||
'assistant.admin.retentionDays.few': '{count} أيام',
|
||||
'assistant.admin.retentionDays.many': '{count} يومًا',
|
||||
'assistant.admin.retentionForever': 'للأبد',
|
||||
'assistant.claude.title': 'الربط مع Claude',
|
||||
'assistant.claude.description': 'استخدم TREK مباشرة داخل Claude: أضفه كموصل، وسجّل الدخول، وانتهى الأمر.',
|
||||
'assistant.claude.open': 'فتح في Claude',
|
||||
'assistant.claude.scan': 'أو امسحه بهاتفك',
|
||||
'assistant.claude.copyUrl': 'نسخ العنوان',
|
||||
'assistant.claude.qrAlt': 'رمز QR لإضافة TREK في Claude',
|
||||
'assistant.claude.notPublic': 'لا يستطيع Claude الوصول إلى TREK إلا إذا كان متاحًا عبر https على الإنترنت.',
|
||||
'assistant.mobile.tile': 'اسأل TREK',
|
||||
'assistant.mobile.tileHint': 'خطط مع المساعد',
|
||||
'assistant.mobile.backToChat': 'العودة إلى المحادثة',
|
||||
'assistant.step.nearbyAt': 'يبحث بالقرب من {place}',
|
||||
'assistant.step.searchingFor': 'يبحث عن {query}',
|
||||
'assistant.step.weatherAt': 'يتحقق من الطقس في {place}',
|
||||
'assistant.change.timeline.show': 'عرض الجدول',
|
||||
'assistant.change.timeline.hide': 'إخفاء الجدول',
|
||||
'assistant.change.timeline.new': 'جديد',
|
||||
'assistant.change.timeline.travel': '≈ {count} دقيقة تنقل',
|
||||
'assistant.change.timeline.travel.zero': '≈ {count} دقيقة تنقل',
|
||||
'assistant.change.timeline.travel.one': '≈ دقيقة تنقل واحدة',
|
||||
'assistant.change.timeline.travel.two': '≈ دقيقتا تنقل',
|
||||
'assistant.change.timeline.travel.few': '≈ {count} دقائق تنقل',
|
||||
'assistant.change.timeline.travel.many': '≈ {count} دقيقة تنقل',
|
||||
'assistant.change.timeline.gap': '{count} دقيقة فراغ',
|
||||
'assistant.change.timeline.gap.zero': '{count} دقيقة فراغ',
|
||||
'assistant.change.timeline.gap.one': 'دقيقة فراغ واحدة',
|
||||
'assistant.change.timeline.gap.two': 'دقيقتا فراغ',
|
||||
'assistant.change.timeline.gap.few': '{count} دقائق فراغ',
|
||||
'assistant.change.timeline.gap.many': '{count} دقيقة فراغ',
|
||||
'assistant.change.timeline.overlap': 'يتداخل مع {place}',
|
||||
'assistant.change.timeline.moreDays': '{count} يوم آخر',
|
||||
'assistant.change.timeline.moreDays.zero': 'لا أيام أخرى',
|
||||
'assistant.change.timeline.moreDays.one': 'يوم آخر',
|
||||
'assistant.change.timeline.moreDays.two': 'يومان آخران',
|
||||
'assistant.change.timeline.moreDays.few': '{count} أيام أخرى',
|
||||
'assistant.change.timeline.moreDays.many': '{count} يومًا آخر',
|
||||
'assistant.change.viewOnMap': 'عرض على الخريطة',
|
||||
'assistant.change.before': 'قبل',
|
||||
'assistant.change.after': 'بعد',
|
||||
'assistant.change.compare': 'قارن على الخريطة',
|
||||
'assistant.change.undoAll': 'تراجع عن الكل',
|
||||
'assistant.change.undoneAll': 'تم التراجع عن {count} تغيير',
|
||||
'assistant.change.undoneAll.zero': 'لم يتم التراجع عن أي تغيير',
|
||||
'assistant.change.undoneAll.one': 'تم التراجع عن تغيير واحد',
|
||||
'assistant.change.undoneAll.two': 'تم التراجع عن تغييرين',
|
||||
'assistant.change.undoneAll.few': 'تم التراجع عن {count} تغييرات',
|
||||
'assistant.change.undoneAll.many': 'تم التراجع عن {count} تغييرًا',
|
||||
'assistant.change.undoAllPartial': 'تم التراجع: {done}، وتغيّر في الأثناء: {stale}',
|
||||
'assistant.change.undoAllFailed': 'تعذّر التراجع عن {count} تغيير',
|
||||
'assistant.change.undoAllFailed.zero': 'لم يتعذّر التراجع عن أي تغيير',
|
||||
'assistant.change.undoAllFailed.one': 'تعذّر التراجع عن تغيير واحد',
|
||||
'assistant.change.undoAllFailed.two': 'تعذّر التراجع عن تغييرين',
|
||||
'assistant.change.undoAllFailed.few': 'تعذّر التراجع عن {count} تغييرات',
|
||||
'assistant.change.undoAllFailed.many': 'تعذّر التراجع عن {count} تغييرًا',
|
||||
'assistant.place.strip': 'الأماكن التي وُجدت',
|
||||
'assistant.place.addTo': 'أضف إلى {day}',
|
||||
'assistant.place.addToShort': '+ اليوم {n}',
|
||||
'assistant.place.pickDay': 'اختر يومًا',
|
||||
'assistant.place.suggested': 'تم الاقتراح',
|
||||
'assistant.place.inTrip': 'موجود في رحلتك بالفعل',
|
||||
'assistant.place.showOnMap': 'على الخريطة',
|
||||
'assistant.place.openNow': 'مفتوح الآن',
|
||||
'assistant.place.closed': 'مغلق',
|
||||
'assistant.place.fromPrev': '{distance} من المحطة السابقة',
|
||||
'assistant.place.more': '{count} أخرى',
|
||||
'assistant.place.more.zero': 'لا مزيد',
|
||||
'assistant.place.more.one': 'واحد آخر',
|
||||
'assistant.place.more.two': 'اثنان آخران',
|
||||
'assistant.place.more.few': '{count} أخرى',
|
||||
'assistant.place.more.many': '{count} أخرى',
|
||||
'assistant.mention.days': 'الأيام',
|
||||
'assistant.mention.places': 'الأماكن',
|
||||
'assistant.mention.bookings': 'الحجوزات',
|
||||
'assistant.mention.none': 'لم يُعثر على شيء',
|
||||
'assistant.mention.hint': 'اكتب @ لذكر الأيام والأماكن والحجوزات.',
|
||||
'assistant.attach.dropPlace': 'أفلت المكان هنا',
|
||||
'assistant.weather.strip': 'الطقس لمدة {count} يوم',
|
||||
'assistant.weather.strip.zero': 'لا أيام للطقس',
|
||||
'assistant.weather.strip.one': 'الطقس ليوم واحد',
|
||||
'assistant.weather.strip.two': 'الطقس ليومين',
|
||||
'assistant.weather.strip.few': 'الطقس لمدة {count} أيام',
|
||||
'assistant.weather.strip.many': 'الطقس لمدة {count} يومًا',
|
||||
'assistant.weather.rain': 'مطر {pct}٪',
|
||||
'assistant.settings.followMap': 'الخريطة تتبع المساعد',
|
||||
'assistant.settings.followMapHint': 'تنتقل الخريطة إلى حيث يبحث المساعد ويقترح الأماكن.',
|
||||
'assistant.error.decideFirst': 'قرّر أولًا بشأن الاقتراحات المفتوحة.',
|
||||
'assistant.error.providerLimit': 'تم بلوغ الحد لدى {provider}. حاول مرة أخرى لاحقًا.',
|
||||
'assistant.hint.title': 'يستحق نظرة',
|
||||
'assistant.hint.dismiss': 'إخفاء هذا التنبيه',
|
||||
'assistant.hint.more': '{count} أخرى',
|
||||
'assistant.hint.more.zero': 'لا مزيد',
|
||||
'assistant.hint.more.one': 'واحد آخر',
|
||||
'assistant.hint.more.two': 'اثنان آخران',
|
||||
'assistant.hint.more.few': '{count} أخرى',
|
||||
'assistant.hint.more.many': '{count} أخرى',
|
||||
'assistant.hint.overlap': '{a} و{b} يتداخلان في {day}',
|
||||
'assistant.hint.overlap.prompt': 'في {day} يتداخل {a} و{b}. كيف نحل ذلك؟',
|
||||
'assistant.hint.closed': '{place} مغلق في {day}',
|
||||
'assistant.hint.closed.prompt': '{place} مغلق في {day}. ماذا نفعل بدلًا من ذلك؟',
|
||||
'assistant.hint.opensLate': '{place} لا يفتح إلا في {time} يوم {day}',
|
||||
'assistant.hint.closesEarly': '{place} يغلق في {time} يوم {day}',
|
||||
'assistant.hint.outsideHours.prompt': 'زيارتنا إلى {place} في {day} لا تناسب ساعات العمل. كيف ننقلها بأفضل شكل؟',
|
||||
'assistant.hint.gap': '{day}: {count} ساعة فراغ من {from}',
|
||||
'assistant.hint.gap.zero': '{day}: لا ساعات فراغ من {from}',
|
||||
'assistant.hint.gap.one': '{day}: ساعة فراغ واحدة من {from}',
|
||||
'assistant.hint.gap.two': '{day}: ساعتا فراغ من {from}',
|
||||
'assistant.hint.gap.few': '{day}: {count} ساعات فراغ من {from}',
|
||||
'assistant.hint.gap.many': '{day}: {count} ساعة فراغ من {from}',
|
||||
'assistant.hint.gap.prompt': 'لا شيء مخطط في {day} من {from} إلى {to}. ماذا يناسب هذا الوقت؟',
|
||||
'assistant.hint.longLeg': 'تفصل {distance} بين {from} و{to} في خط مستقيم',
|
||||
'assistant.hint.longLeg.prompt': 'هل المسافة من {from} إلى {to} في {day} بعيدة جدًا؟ ما أفضل طريقة للوصول؟',
|
||||
'assistant.hint.tightLeg': '{count} دقيقة فقط بين {from} و{to}',
|
||||
'assistant.hint.tightLeg.zero': 'لا وقت بين {from} و{to}',
|
||||
'assistant.hint.tightLeg.one': 'دقيقة واحدة فقط بين {from} و{to}',
|
||||
'assistant.hint.tightLeg.two': 'دقيقتان فقط بين {from} و{to}',
|
||||
'assistant.hint.tightLeg.few': '{count} دقائق فقط بين {from} و{to}',
|
||||
'assistant.hint.tightLeg.many': '{count} دقيقة فقط بين {from} و{to}',
|
||||
'assistant.hint.tightLeg.prompt': 'هل يكفي الوقت في {day} للانتقال من {from} إلى {to}؟ ماذا نغيّر؟',
|
||||
'assistant.hint.noStay': 'لا مكان للإقامة بعد لمدة {count} ليلة من {day}',
|
||||
'assistant.hint.noStay.zero': 'لا ليالٍ بلا إقامة من {day}',
|
||||
'assistant.hint.noStay.one': 'لا مكان للإقامة بعد لليلة التي تلي {day}',
|
||||
'assistant.hint.noStay.two': 'لا مكان للإقامة بعد لليلتين من {day}',
|
||||
'assistant.hint.noStay.few': 'لا مكان للإقامة بعد لمدة {count} ليالٍ من {day}',
|
||||
'assistant.hint.noStay.many': 'لا مكان للإقامة بعد لمدة {count} ليلة من {day}',
|
||||
'assistant.hint.noStay.prompt': 'ما زلنا نحتاج إلى مكان للإقامة من {day}. ماذا تقترح؟',
|
||||
'assistant.hint.unplacedBooking': '{title} غير مرتبط بأي يوم بعد',
|
||||
'assistant.hint.unplacedBookingDay': '{title} يخص على الأرجح {day}',
|
||||
'assistant.hint.unplacedBookings': '{count} حجز غير مرتبط بأي يوم بعد',
|
||||
'assistant.hint.unplacedBookings.zero': 'لا حجوزات بلا يوم',
|
||||
'assistant.hint.unplacedBookings.one': 'حجز واحد غير مرتبط بأي يوم بعد',
|
||||
'assistant.hint.unplacedBookings.two': 'حجزان غير مرتبطين بأي يوم بعد',
|
||||
'assistant.hint.unplacedBookings.few': '{count} حجوزات غير مرتبطة بأي يوم بعد',
|
||||
'assistant.hint.unplacedBookings.many': '{count} حجزًا غير مرتبط بأي يوم بعد',
|
||||
'assistant.hint.unplacedBooking.prompt': 'ما الحجوزات غير المرتبطة بأي يوم، وأين مكانها؟',
|
||||
'assistant.hint.menuOn': 'إظهار التنبيهات',
|
||||
'assistant.hint.menuOff': 'إيقاف إظهار التنبيهات',
|
||||
'assistant.launcher.withHints': 'المساعد، {count} تنبيه',
|
||||
'assistant.launcher.withHints.zero': 'المساعد، لا تنبيهات',
|
||||
'assistant.launcher.withHints.one': 'المساعد، تنبيه واحد',
|
||||
'assistant.launcher.withHints.two': 'المساعد، تنبيهان',
|
||||
'assistant.launcher.withHints.few': 'المساعد، {count} تنبيهات',
|
||||
'assistant.launcher.withHints.many': 'المساعد، {count} تنبيهًا',
|
||||
'assistant.mobile.tileHints': '{count} أمر للمراجعة في خطتك',
|
||||
'assistant.mobile.tileHints.zero': 'لا شيء للمراجعة في خطتك',
|
||||
'assistant.mobile.tileHints.one': 'أمر واحد للمراجعة في خطتك',
|
||||
'assistant.mobile.tileHints.two': 'أمران للمراجعة في خطتك',
|
||||
'assistant.mobile.tileHints.few': '{count} أمور للمراجعة في خطتك',
|
||||
'assistant.mobile.tileHints.many': '{count} أمرًا للمراجعة في خطتك',
|
||||
'assistant.share.button': 'مشاركة في محادثة الرحلة',
|
||||
'assistant.share.title': 'مشاركة في محادثة الرحلة',
|
||||
'assistant.share.who': 'سيرى كل من في الرحلة هذه الرسالة، مع إشارة إلى أنها من المساعد.',
|
||||
'assistant.share.includeChanges': 'إرفاق التغييرات التي طبّقتها',
|
||||
'assistant.share.changesHeading': 'تم تطبيقه:',
|
||||
'assistant.share.truncated': 'اختُصر إلى {count} حرف',
|
||||
'assistant.share.truncated.zero': 'اختُصر إلى {count} حرف',
|
||||
'assistant.share.truncated.one': 'اختُصر إلى حرف واحد',
|
||||
'assistant.share.truncated.two': 'اختُصر إلى حرفين',
|
||||
'assistant.share.truncated.few': 'اختُصر إلى {count} أحرف',
|
||||
'assistant.share.truncated.many': 'اختُصر إلى {count} حرفًا',
|
||||
'assistant.share.send': 'مشاركة',
|
||||
'assistant.share.done': 'تمت المشاركة في محادثة الرحلة',
|
||||
'assistant.share.shared': 'تمت المشاركة',
|
||||
'assistant.share.failed': 'تعذّرت المشاركة. حاول مرة أخرى بعد قليل.',
|
||||
'assistant.share.chatOff': 'محادثة الرحلة متوقفة.',
|
||||
};
|
||||
export default assistant;
|
||||
@@ -126,5 +126,7 @@ const collab: TranslationStrings = {
|
||||
'collab.chat.attachImages': 'إرفاق صور',
|
||||
'collab.chat.attachedImage': 'صورة مرفقة',
|
||||
'collab.chat.closeImage': 'إغلاق الصورة',
|
||||
'collab.chat.fromAssistant': 'من المساعد',
|
||||
'collab.chat.fromAssistantHint': 'شارك {name} هذه الإجابة من المساعد',
|
||||
};
|
||||
export default collab;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import admin from './admin';
|
||||
import airport from './airport';
|
||||
import assistant from './assistant';
|
||||
import atlas from './atlas';
|
||||
import backup from './backup';
|
||||
import budget from './budget';
|
||||
@@ -116,5 +117,6 @@ const locale = {
|
||||
...dawarich,
|
||||
...docsync,
|
||||
...ferratas,
|
||||
...assistant,
|
||||
};
|
||||
export default locale;
|
||||
|
||||
@@ -82,6 +82,7 @@ const oauth: TranslationStrings = {
|
||||
'oauth.authorize.loginButton': 'Sign in to TREK', // en-fallback
|
||||
'oauth.authorize.requestLabel': 'Authorization Request', // en-fallback
|
||||
'oauth.authorize.requestDescription': 'This application is requesting access to your TREK account.', // en-fallback
|
||||
'oauth.authorize.verifiedHost': 'نطاق موثَّق: {host}',
|
||||
'oauth.authorize.trustNote': 'Only grant access to applications you trust. Your data stays on your server.', // en-fallback
|
||||
'oauth.authorize.selectScope': 'Select at least one scope', // en-fallback
|
||||
'oauth.authorize.approveScopes': 'موافقة ({count} نطاق)',
|
||||
|
||||
@@ -725,6 +725,9 @@ const admin: TranslationStrings = {
|
||||
'admin.addons.catalog.llm_parsing.name': 'Süni intellektlə təhlil',
|
||||
'admin.addons.catalog.llm_parsing.description':
|
||||
'Daxili təhlilçinin oxuya bilmədiyi rezervasiyaları seçdiyiniz süni intellekt modeli ilə oxuyur',
|
||||
'admin.addons.catalog.ai_assistant.name': 'Süni intellekt köməkçisi',
|
||||
'admin.addons.catalog.ai_assistant.description':
|
||||
'Hər səfərdə sualları cavablandıran və təsdiqlədiyiniz dəyişikliklər təklif edən söhbət',
|
||||
'admin.addons.llm.vision.auto': 'Avtomatik',
|
||||
'admin.addons.llm.vision.on': 'Bəli',
|
||||
'admin.addons.llm.vision.off': 'Xeyr',
|
||||
|
||||
@@ -0,0 +1,343 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistant: TranslationStrings = {
|
||||
'assistant.launcher.tooltip': 'Köməkçi',
|
||||
'assistant.launcher.ctrlKey': 'Ctrl', // same-as-en
|
||||
'assistant.picker.title': 'Hansı səyahət?',
|
||||
'assistant.picker.empty': 'Əvvəlcə səyahət yaradın, sonra köməkçi onu planlaşdırmağa kömək edəcək.',
|
||||
'assistant.picker.now': 'İndi',
|
||||
'assistant.picker.next': 'Növbəti',
|
||||
'assistant.window.title': 'Köməkçi',
|
||||
'assistant.window.log': 'Köməkçi ilə söhbət',
|
||||
'assistant.window.menu': 'Digər seçimlər',
|
||||
'assistant.window.settings': 'Süni intellekt ayarları',
|
||||
'assistant.window.deleteThread': 'Söhbəti sil',
|
||||
'assistant.window.deleteConfirm': 'Bu söhbət silinsin? Bunu geri qaytarmaq olmaz.',
|
||||
'assistant.setup.question': 'Nə ilə söhbət etmək istəyirsiniz?',
|
||||
'assistant.setup.beta': 'Beta', // same-as-en
|
||||
'assistant.setup.chatgptNote': 'API açarı rəsmi yoldur. ChatGPT ilə daxil olmaq hələ beta mərhələsindədir.',
|
||||
'assistant.setup.keySubtitle': 'Anthropic, OpenAI, Gemini və ya OpenRouter',
|
||||
'assistant.setup.adminOnly': 'Bunu administratorunuz qurur.',
|
||||
'assistant.setup.forEveryone': 'Hamı üçün qur',
|
||||
'assistant.connect.title': 'Süni intellekt köməkçisi',
|
||||
'assistant.connect.description':
|
||||
'Səyahətinizi tanıyan və təsdiqlədiyiniz dəyişikliklər təklif edən köməkçi ilə söhbət edin.',
|
||||
'assistant.connect.chatgpt': 'ChatGPT ilə daxil ol',
|
||||
'assistant.connect.chatgptHint': 'ChatGPT planınızdan istifadə edir. Açar lazım deyil.',
|
||||
'assistant.connect.chatgptStep1': 'Yeni tabda ChatGPT-yə daxil olun.',
|
||||
'assistant.connect.chatgptOpen': 'ChatGPT girişini aç',
|
||||
'assistant.connect.chatgptStep2':
|
||||
'Sonra brauzer xəta səhifəsi göstərəcək. Bu gözləniləndir. Yuxarıdakı ünvanı kopyalayıb geri qayıdın.',
|
||||
'assistant.connect.chatgptPaste': 'Ünvanı yapışdır',
|
||||
'assistant.connect.chatgptWrong': 'Bu düzgün ünvan deyil. O, 127.0.0.1 ilə başlayır.',
|
||||
'assistant.connect.chatgptExpired': 'Çox uzun çəkdi. Yenidən daxil olun.',
|
||||
'assistant.connect.chatgptOtherAccount': 'Bu, başqa ChatGPT hesabıdır. Əvvəlcə bağlantını silin.',
|
||||
'assistant.connect.key': 'API açarını yapışdır',
|
||||
'assistant.connect.keyPlaceholder': 'Açarınızı bura yapışdırın',
|
||||
'assistant.connect.detected': 'Tanındı: {provider}',
|
||||
'assistant.connect.whichProvider': 'Bu açar hansı provayderdəndir?',
|
||||
'assistant.connect.testing': 'Bağlantı yoxlanılır',
|
||||
'assistant.connect.ok': 'Qoşuldu. Başlayaq.',
|
||||
'assistant.connect.openAssistant': 'Köməkçini aç',
|
||||
'assistant.connect.badKey': 'Bu açar işləmir.',
|
||||
'assistant.connect.unknownModel': 'Bu model burada təklif olunmur.',
|
||||
'assistant.connect.getKey': 'Açar əldə et',
|
||||
'assistant.connect.hintAnthropic': 'Claude Max və Team planlarına API kreditləri daxildir.',
|
||||
'assistant.connect.hintOpenai': 'OpenAI panelinizdə açar yaradın.',
|
||||
'assistant.connect.hintGemini': 'AI Studio-da dəqiqəlik və günlük limitləri olan pulsuz səviyyə var; orada Google göndərdiklərinizi məhsullarını yaxşılaşdırmaq üçün istifadə edə bilər. Ödəniş aktiv olduqda limitlər daha yüksəkdir və bu tətbiq olunmur.',
|
||||
'assistant.connect.hintOpenrouter': 'OpenRouter ilə daxil olun, açar kopyalamağa ehtiyac yoxdur.',
|
||||
'assistant.connect.openrouter': 'OpenRouter-i qoş',
|
||||
'assistant.connect.save': 'Qoş',
|
||||
'assistant.connect.model': 'Model', // same-as-en
|
||||
'assistant.connect.modelHint': 'Yalnız alətlərdən istifadə edə bilən modellər göstərilir.',
|
||||
'assistant.connect.free': 'Pulsuz səviyyə var',
|
||||
'assistant.connect.paid': 'İstifadəyə görə ödəniş',
|
||||
'assistant.model.recommended': 'Tövsiyə olunur',
|
||||
'assistant.model.free': 'Pulsuz',
|
||||
'assistant.connect.connected': '{provider} ilə qoşulub',
|
||||
'assistant.connect.providedByAdmin': 'Administratorunuz tərəfindən təmin edilir',
|
||||
'assistant.connect.disconnect': 'Ayır',
|
||||
'assistant.connect.disconnectConfirm': '{provider} ayrılsın? Söhbətləriniz qalır.',
|
||||
'assistant.connect.signOut': 'Çıxış',
|
||||
'assistant.connect.statusOk': 'Qoşulub',
|
||||
'assistant.connect.statusNeedsReauth': 'Girişin vaxtı bitib',
|
||||
'assistant.connect.statusSignedOut': 'Çıxış edilib',
|
||||
'assistant.connect.statusError': 'Əlçatan deyil',
|
||||
'assistant.connect.quota': 'Bu gün {count} mesajdan {used} istifadə olunub',
|
||||
'assistant.connect.quota.one': 'Bu gün {count} mesajdan {used} istifadə olunub',
|
||||
'assistant.connect.claudeLink': 'Claude istifadə edirsiniz? Claude-da qoşulun',
|
||||
'assistant.privacy.what': 'Nə paylaşılır?',
|
||||
'assistant.privacy.banner': 'Mesajlarınız və köməkçinin oxuduğu səyahət məlumatları {provider} xidmətinə göndərilir.',
|
||||
'assistant.privacy.ok': 'Aydındır',
|
||||
'assistant.privacy.shared':
|
||||
'Mesajlarınız və köməkçinin oxuduğu səyahət təfərrüatları, məsələn, yerlər, günlər və bronlar.',
|
||||
'assistant.privacy.notShared': 'Parolunuz, fayllarınız və digər səyahətləriniz yox.',
|
||||
'assistant.privacy.provider': '{provider} bunları öz məxfilik şərtlərinə uyğun emal edir.',
|
||||
'assistant.empty.title': 'Nəyi planlaşdırırıq?',
|
||||
'assistant.empty.readOnly':
|
||||
'Bu səyahətə yalnız baxa bilərsiniz, ona görə suallara cavab verə bilərəm, amma heç nəyi dəyişə bilmərəm.',
|
||||
'assistant.suggest.planFirstDays': 'İlk günləri mənim üçün planlaşdır',
|
||||
'assistant.suggest.mustSee': 'Burada nəyi qaçırmamalıyam?',
|
||||
'assistant.suggest.packingList': 'Bu səyahət üçün əşya siyahısı hazırla',
|
||||
'assistant.suggest.emptyDays': 'Hansı günlər hələ boşdur?',
|
||||
'assistant.suggest.rainPlan': 'Yağış yağsa, nə edə bilərik?',
|
||||
'assistant.suggest.today': 'Bu gün nə var?',
|
||||
'assistant.suggest.eatNearby': 'Yaxınlıqda yemək üçün yer tap',
|
||||
'assistant.suggest.weather': 'Hava necə olacaq?',
|
||||
'assistant.suggest.openTodos': 'Tapşırıq siyahısında hələ nə qalıb?',
|
||||
'assistant.suggest.settleUp': 'Kim kimə borcludur?',
|
||||
'assistant.suggest.recap': 'Səyahətimizi xülasə et',
|
||||
'assistant.suggest.nearPlace': '{place} yaxınlığında daha nə var?',
|
||||
'assistant.suggest.placeTips': '{place} ziyarəti üçün məsləhətlər',
|
||||
'assistant.suggest.planDay': '{day} günü mənim üçün planlaşdır',
|
||||
'assistant.suggest.dayTooFull': '{day} çox dolu deyil ki?',
|
||||
'assistant.thread.scrollDown': 'Son mesaja keç',
|
||||
'assistant.attach.button': 'Rezervasiya əlavə et',
|
||||
'assistant.attach.drop': 'Buraya burax, rezervasiyanı oxuyum',
|
||||
'assistant.attach.unsupported': 'Bu fayl növü oxuna bilmir. PDF, e-poçt, şəkil və ya wallet kartı yoxla.',
|
||||
'assistant.attach.tooLarge': 'Bu fayl 10 MB-dan böyükdür.',
|
||||
'assistant.composer.placeholder': '{trip} haqqında istənilən sualı verin',
|
||||
'assistant.composer.send': 'Göndər',
|
||||
'assistant.composer.stop': 'Dayandır',
|
||||
'assistant.composer.decideFirst': 'Əvvəlcə yuxarıdakı dəyişiklikləri tətbiq edin və ya ləğv edin.',
|
||||
'assistant.context.ask': 'Soruş',
|
||||
'assistant.context.remove': 'Konteksti sil',
|
||||
'assistant.step.reading': 'Planınız oxunur',
|
||||
'assistant.step.searching': 'Yerlər axtarılır',
|
||||
'assistant.step.details': 'Təfərrüatlar yoxlanılır',
|
||||
'assistant.step.nearby': 'Yaxınlıqda axtarılır',
|
||||
'assistant.step.categories': 'Kateqoriyalar yoxlanılır',
|
||||
'assistant.step.weather': 'Hava yoxlanılır',
|
||||
'assistant.step.todos': 'Tapşırıq siyahısı oxunur',
|
||||
'assistant.step.costs': 'Xərclər yoxlanılır',
|
||||
'assistant.step.help': 'Kömək bölməsində axtarılır',
|
||||
'assistant.step.readingBooking': 'Bron oxunur',
|
||||
'assistant.step.working': 'Üzərində işləyirəm',
|
||||
'assistant.step.collapsed': '{count} addım',
|
||||
'assistant.step.collapsed.one': '{count} addım',
|
||||
'assistant.change.title': 'Təklif olunan dəyişikliklər',
|
||||
'assistant.change.doneTitle': 'Hazırdır',
|
||||
'assistant.change.apply': 'Tətbiq et',
|
||||
'assistant.change.applyN': '{count} tətbiq et',
|
||||
'assistant.change.applyN.one': '{count} tətbiq et',
|
||||
'assistant.change.discard': 'Ləğv et',
|
||||
'assistant.change.include': 'Bu dəyişikliyi daxil et',
|
||||
'assistant.change.applied': 'Tətbiq edildi',
|
||||
'assistant.change.discarded': 'Ləğv edildi',
|
||||
'assistant.change.failed': 'Bunu tətbiq etmək olmadı.',
|
||||
'assistant.change.applying': 'Tətbiq edilir',
|
||||
'assistant.change.expired': 'Artıq mövcud deyil',
|
||||
'assistant.change.undo': 'Geri al',
|
||||
'assistant.change.undone': 'Geri alındı',
|
||||
'assistant.change.undoStale': 'Bu arada dəyişdirilib. Zəhmət olmasa, əl ilə düzəldin.',
|
||||
'assistant.change.undoFailed': 'Bunu geri almaq olmadı.',
|
||||
'assistant.change.showOnMap': 'Xəritədə göstər',
|
||||
'assistant.change.empty': 'boş',
|
||||
'assistant.change.pending': '{count} dəyişiklik sizi gözləyir',
|
||||
'assistant.change.pending.one': '{count} dəyişiklik sizi gözləyir',
|
||||
'assistant.change.verb.addPlace': 'Yer əlavə et',
|
||||
'assistant.change.verb.addPlaceToDay': 'Günə yer əlavə et',
|
||||
'assistant.change.verb.updatePlace': 'Yeri dəyiş',
|
||||
'assistant.change.verb.assignPlace': 'Yeri günə planlaşdır',
|
||||
'assistant.change.verb.moveAssignment': 'Başqa günə köçür',
|
||||
'assistant.change.verb.removeFromDay': 'Gündən çıxar',
|
||||
'assistant.change.verb.addDayNote': 'Qeyd əlavə et',
|
||||
'assistant.change.verb.updateDayNote': 'Qeydi dəyiş',
|
||||
'assistant.change.verb.addPackingItem': 'Əşya siyahısına əlavə et',
|
||||
'assistant.change.verb.updatePackingItem': 'Əşyanı dəyiş',
|
||||
'assistant.change.verb.addTodo': 'Tapşırıq əlavə et',
|
||||
'assistant.change.verb.addBudgetItem': 'Xərc əlavə et',
|
||||
'assistant.change.verb.addReservation': 'Bron əlavə et',
|
||||
'assistant.change.verb.importBooking': 'Fayldan bron əlavə et',
|
||||
'assistant.change.label.name': 'Ad',
|
||||
'assistant.change.label.description': 'Təsvir',
|
||||
'assistant.change.label.address': 'Ünvan',
|
||||
'assistant.change.label.coordinates': 'Xəritədə yer',
|
||||
'assistant.change.label.category': 'Kateqoriya',
|
||||
'assistant.change.label.day': 'Gün',
|
||||
'assistant.change.label.time': 'Vaxt',
|
||||
'assistant.change.label.endTime': 'Bitmə vaxtı',
|
||||
'assistant.change.label.notes': 'Qeydlər',
|
||||
'assistant.change.label.website': 'Vebsayt',
|
||||
'assistant.change.label.phone': 'Telefon',
|
||||
'assistant.change.label.email': 'E-poçt',
|
||||
'assistant.change.label.openingHours': 'İş saatları',
|
||||
'assistant.change.label.price': 'Qiymət',
|
||||
'assistant.change.label.currency': 'Valyuta',
|
||||
'assistant.change.label.stopType': 'Dayanacaq növü',
|
||||
'assistant.change.label.text': 'Mətn',
|
||||
'assistant.change.label.icon': 'İkon',
|
||||
'assistant.change.label.color': 'Rəng',
|
||||
'assistant.change.label.position': 'Sıra',
|
||||
'assistant.change.label.quantity': 'Miqdar',
|
||||
'assistant.change.label.weight': 'Çəki',
|
||||
'assistant.change.label.bag': 'Çanta',
|
||||
'assistant.change.label.packed': 'Yığılıb',
|
||||
'assistant.change.label.dueDate': 'Son tarix',
|
||||
'assistant.change.label.priority': 'Prioritet',
|
||||
'assistant.change.label.assignee': 'Təyin edilib',
|
||||
'assistant.change.label.type': 'Növ',
|
||||
'assistant.change.label.location': 'Yer',
|
||||
'assistant.change.label.link': 'Keçid',
|
||||
'assistant.change.label.confirmation': 'Təsdiq nömrəsi',
|
||||
'assistant.change.label.checkIn': 'Giriş',
|
||||
'assistant.change.label.checkOut': 'Çıxış',
|
||||
'assistant.change.label.stay': 'Qalma',
|
||||
'assistant.change.label.members': 'Bölünür',
|
||||
'assistant.change.label.route': 'Marşrut',
|
||||
'assistant.history.open': 'Son söhbətlər',
|
||||
'assistant.history.new': 'Yeni söhbət',
|
||||
'assistant.history.empty': 'Hələ söhbət yoxdur.',
|
||||
'assistant.history.retention': 'Söhbətlər {count} gündən sonra silinir.',
|
||||
'assistant.history.retention.one': 'Söhbətlər {count} gündən sonra silinir.',
|
||||
'assistant.history.forever': 'Söhbətlər siz silənə qədər saxlanılır.',
|
||||
'assistant.error.notConnected': 'Köməkçi hələ qoşulmayıb.',
|
||||
'assistant.error.busy': 'Artıq başqa pəncərədə işləyir.',
|
||||
'assistant.error.dailyLimit': 'Bu günlük bu qədər. Sabah görüşərik.',
|
||||
'assistant.error.rateLimited': 'Bir az yavaş. Bir dəqiqədən sonra yenidən cəhd edin.',
|
||||
'assistant.error.needsReauth': '{provider} girişinizin vaxtı bitib.',
|
||||
'assistant.error.usageLimit': 'ChatGPT istifadə limitiniz bitib.',
|
||||
'assistant.error.notEligible': 'Bu ChatGPT hesabı burada istifadə edilə bilməz.',
|
||||
'assistant.error.invalidKey': '{provider} açarınız artıq işləmir.',
|
||||
'assistant.error.providerDown': '{provider} hazırda cavab vermir.',
|
||||
'assistant.error.maxSteps': 'Bir dəfəyə çox oldu. Davam edim?',
|
||||
'assistant.error.offline': 'Oflayn. Köməkçinin internetə ehtiyacı var.',
|
||||
'assistant.error.noBookingsFound': 'Bu faylda bron tapılmadı.',
|
||||
'assistant.error.importUnavailable': 'Bron fayllarının oxunması burada mümkün deyil.',
|
||||
'assistant.error.generic': 'Nəsə səhv getdi.',
|
||||
'assistant.errorAction.retry': 'Yenidən cəhd et',
|
||||
'assistant.errorAction.continueHere': 'Burada davam et',
|
||||
'assistant.errorAction.continue': 'Davam et',
|
||||
'assistant.errorAction.signIn': 'Yenidən daxil ol',
|
||||
'assistant.errorAction.viewUsage': 'İstifadəyə bax',
|
||||
'assistant.errorAction.otherProvider': 'Başqa provayder seç',
|
||||
'assistant.errorAction.replaceKey': 'Açarı dəyiş',
|
||||
'assistant.errorAction.connectOwn': 'Öz provayderini qoş',
|
||||
'assistant.admin.policy': 'Kim ödəyir',
|
||||
'assistant.admin.policy.instance': 'Hamı üçün mən ödəyirəm',
|
||||
'assistant.admin.policy.instanceOrUser': 'Mən ödəyirəm, şəxsi icazəlidir',
|
||||
'assistant.admin.policy.user': 'Hər kəs öz hesabından istifadə edir',
|
||||
'assistant.admin.policy.instanceHint': 'Hamı aşağıdakı bağlantıdan istifadə edir və özününkünü əlavə edə bilməz.',
|
||||
'assistant.admin.policy.instanceOrUserHint': 'Hamı aşağıdakı bağlantıdan istifadə edir, əgər özününkünü qoşmayıbsa.',
|
||||
'assistant.admin.policy.userHint': 'Hər kəs öz provayderini qoşur. Sizdən heç nə tutulmur.',
|
||||
'assistant.admin.connection': 'Hamı üçün bağlantı',
|
||||
'assistant.admin.connectionNone': 'Hələ bağlantı yoxdur.',
|
||||
'assistant.admin.connectionRemove': 'Bağlantını sil',
|
||||
'assistant.admin.custom': 'Öz server',
|
||||
'assistant.admin.customHint': 'İstənilən OpenAI ilə uyğun server, məsələn, Ollama.',
|
||||
'assistant.admin.baseUrl': 'Server ünvanı',
|
||||
'assistant.admin.apiKeyOptional': 'API açarı (istəyə bağlı)',
|
||||
'assistant.admin.prefilled': 'Süni intellektlə təhlildən götürülüb.',
|
||||
'assistant.admin.dailyLimit': 'Bir nəfər üçün gündəlik mesaj',
|
||||
'assistant.admin.dailyLimitHint': 'Yalnız siz ödədiyiniz müddətdə sayılır.',
|
||||
'assistant.admin.retention': 'Söhbətləri saxla',
|
||||
'assistant.admin.retentionDays': '{count} gün',
|
||||
'assistant.admin.retentionDays.one': '{count} gün',
|
||||
'assistant.admin.retentionForever': 'Həmişəlik',
|
||||
'assistant.claude.title': 'Claude ilə qoşul',
|
||||
'assistant.claude.description':
|
||||
'TREK-dən birbaşa Claude daxilində istifadə edin: onu konnektor kimi əlavə edin, daxil olun, hazırdır.',
|
||||
'assistant.claude.open': 'Claude-da aç',
|
||||
'assistant.claude.scan': 'Və ya telefonunuzla skan edin',
|
||||
'assistant.claude.copyUrl': 'Ünvanı kopyala',
|
||||
'assistant.claude.qrAlt': 'TREK-i Claude-a əlavə etmək üçün QR kod',
|
||||
'assistant.claude.notPublic': 'Claude TREK-ə yalnız o, internetdə https ilə əlçatan olduqda çata bilər.',
|
||||
'assistant.mobile.tile': 'TREK-dən soruş',
|
||||
'assistant.mobile.tileHint': 'Köməkçi ilə planlaşdırın',
|
||||
'assistant.mobile.backToChat': 'Söhbətə qayıt',
|
||||
'assistant.step.nearbyAt': '{place} yaxınlığında axtarır',
|
||||
'assistant.step.searchingFor': '{query} axtarır',
|
||||
'assistant.step.weatherAt': '{place} üçün havaya baxır',
|
||||
'assistant.change.timeline.show': 'Cədvəli göstər',
|
||||
'assistant.change.timeline.hide': 'Cədvəli gizlət',
|
||||
'assistant.change.timeline.new': 'Yeni',
|
||||
'assistant.change.timeline.travel': '≈ {count} dəq yol',
|
||||
'assistant.change.timeline.travel.one': '≈ {count} dəq yol',
|
||||
'assistant.change.timeline.gap': '{count} dəq boş',
|
||||
'assistant.change.timeline.gap.one': '{count} dəq boş',
|
||||
'assistant.change.timeline.overlap': '{place} ilə üst-üstə düşür',
|
||||
'assistant.change.timeline.moreDays': 'Daha {count} gün',
|
||||
'assistant.change.timeline.moreDays.one': 'Daha {count} gün',
|
||||
'assistant.change.viewOnMap': 'Xəritədə bax',
|
||||
'assistant.change.before': 'Əvvəl',
|
||||
'assistant.change.after': 'Sonra',
|
||||
'assistant.change.compare': 'Xəritədə müqayisə et',
|
||||
'assistant.change.undoAll': 'Hamısını geri al',
|
||||
'assistant.change.undoneAll': '{count} dəyişiklik geri alındı',
|
||||
'assistant.change.undoneAll.one': '{count} dəyişiklik geri alındı',
|
||||
'assistant.change.undoAllPartial': 'Geri alındı: {done}; bu arada dəyişib: {stale}',
|
||||
'assistant.change.undoAllFailed': '{count} dəyişikliyi geri almaq mümkün olmadı',
|
||||
'assistant.change.undoAllFailed.one': '{count} dəyişikliyi geri almaq mümkün olmadı',
|
||||
'assistant.place.strip': 'Tapılan yerlər',
|
||||
'assistant.place.addTo': '{day} gününə əlavə et',
|
||||
'assistant.place.addToShort': '+ {n}. gün',
|
||||
'assistant.place.pickDay': 'Gün seçin',
|
||||
'assistant.place.suggested': 'Təklif edildi',
|
||||
'assistant.place.inTrip': 'Artıq səyahətinizdədir',
|
||||
'assistant.place.showOnMap': 'Xəritədə',
|
||||
'assistant.place.openNow': 'İndi açıqdır',
|
||||
'assistant.place.closed': 'Bağlıdır',
|
||||
'assistant.place.fromPrev': 'Əvvəlki dayanacaqdan {distance}',
|
||||
'assistant.place.more': 'Daha {count}',
|
||||
'assistant.place.more.one': 'Daha {count}',
|
||||
'assistant.mention.days': 'Günlər',
|
||||
'assistant.mention.places': 'Yerlər',
|
||||
'assistant.mention.bookings': 'Rezervasiyalar',
|
||||
'assistant.mention.none': 'Heç nə tapılmadı',
|
||||
'assistant.mention.hint': 'Günləri, yerləri və rezervasiyaları qeyd etmək üçün @ yazın.',
|
||||
'assistant.attach.dropPlace': 'Yeri bura buraxın',
|
||||
'assistant.weather.strip': '{count} gün üçün hava',
|
||||
'assistant.weather.strip.one': '{count} gün üçün hava',
|
||||
'assistant.weather.rain': '{pct}% yağış',
|
||||
'assistant.settings.followMap': 'Xəritə köməkçini izləyir',
|
||||
'assistant.settings.followMapHint': 'Xəritə köməkçinin axtardığı və yer təklif etdiyi yerə sürüşür.',
|
||||
'assistant.error.decideFirst': 'Əvvəlcə açıq təkliflər barədə qərar verin.',
|
||||
'assistant.error.providerLimit': '{provider} limitinə çatıldı. Sonra yenidən cəhd edin.',
|
||||
'assistant.hint.title': 'Baxmağa dəyər',
|
||||
'assistant.hint.dismiss': 'Bu ipucunu gizlət',
|
||||
'assistant.hint.more': 'Daha {count}',
|
||||
'assistant.hint.more.one': 'Daha {count}',
|
||||
'assistant.hint.overlap': '{a} və {b} {day} günü üst-üstə düşür',
|
||||
'assistant.hint.overlap.prompt': '{day} günü {a} və {b} üst-üstə düşür. Bunu necə həll edək?',
|
||||
'assistant.hint.closed': '{place} {day} günü bağlıdır',
|
||||
'assistant.hint.closed.prompt': '{place} {day} günü bağlıdır. Əvəzində nə edək?',
|
||||
'assistant.hint.opensLate': '{place} {day} günü yalnız {time} açılır',
|
||||
'assistant.hint.closesEarly': '{place} {day} günü artıq {time} bağlanır',
|
||||
'assistant.hint.outsideHours.prompt':
|
||||
'{day} günü {place} səfərimiz iş saatlarına uyğun gəlmir. Onu necə yaxşı köçürək?',
|
||||
'assistant.hint.gap': '{day}: {from} saatından {count} saat boşdur',
|
||||
'assistant.hint.gap.one': '{day}: {from} saatından {count} saat boşdur',
|
||||
'assistant.hint.gap.prompt': '{day} günü {from} ilə {to} arası heç nə planlaşdırılmayıb. Ora nə sığar?',
|
||||
'assistant.hint.longLeg': '{from} və {to} arasında düz xətlə {distance} var',
|
||||
'assistant.hint.longLeg.prompt': '{day} günü {from} ilə {to} arası çox uzaqdır? Ora necə yaxşı gedək?',
|
||||
'assistant.hint.tightLeg': '{from} ilə {to} arasında cəmi {count} dəqiqə var',
|
||||
'assistant.hint.tightLeg.one': '{from} ilə {to} arasında cəmi {count} dəqiqə var',
|
||||
'assistant.hint.tightLeg.prompt': '{day} günü {from} yerindən {to} yerinə çatmağa vaxt çatır? Nəyi dəyişək?',
|
||||
'assistant.hint.noStay': '{day} günündən {count} gecə üçün hələ qalacaq yer yoxdur',
|
||||
'assistant.hint.noStay.one': '{day} günündən {count} gecə üçün hələ qalacaq yer yoxdur',
|
||||
'assistant.hint.noStay.prompt': '{day} günündən hələ qalacaq yerimiz yoxdur. Nə təklif edirsiniz?',
|
||||
'assistant.hint.unplacedBooking': '{title} hələ heç bir günə bağlı deyil',
|
||||
'assistant.hint.unplacedBookingDay': '{title} yəqin ki, {day} gününə aiddir',
|
||||
'assistant.hint.unplacedBookings': '{count} rezervasiya hələ heç bir günə bağlı deyil',
|
||||
'assistant.hint.unplacedBookings.one': '{count} rezervasiya hələ heç bir günə bağlı deyil',
|
||||
'assistant.hint.unplacedBooking.prompt': 'Hansı rezervasiyalar hələ heç bir günə bağlı deyil və hara aiddir?',
|
||||
'assistant.hint.menuOn': 'İpuçlarını göstər',
|
||||
'assistant.hint.menuOff': 'İpuçlarını göstərmə',
|
||||
'assistant.launcher.withHints': 'Köməkçi, {count} ipucu',
|
||||
'assistant.launcher.withHints.one': 'Köməkçi, {count} ipucu',
|
||||
'assistant.mobile.tileHints': 'Planınızda yoxlanacaq {count} şey',
|
||||
'assistant.mobile.tileHints.one': 'Planınızda yoxlanacaq {count} şey',
|
||||
'assistant.share.button': 'Səyahət söhbətində paylaş',
|
||||
'assistant.share.title': 'Səyahət söhbətində paylaş',
|
||||
'assistant.share.who': 'Səyahətdəki hər kəs bu mesajı köməkçidən gəldiyi qeydi ilə görəcək.',
|
||||
'assistant.share.includeChanges': 'Tətbiq olunan dəyişiklikləri əlavə et',
|
||||
'assistant.share.changesHeading': 'Tətbiq olundu:',
|
||||
'assistant.share.truncated': '{count} simvola qədər qısaldıldı',
|
||||
'assistant.share.truncated.one': '{count} simvola qədər qısaldıldı',
|
||||
'assistant.share.send': 'Paylaş',
|
||||
'assistant.share.done': 'Səyahət söhbətində paylaşıldı',
|
||||
'assistant.share.shared': 'Paylaşıldı',
|
||||
'assistant.share.failed': 'Paylaşmaq alınmadı. Bir azdan yenidən cəhd edin.',
|
||||
'assistant.share.chatOff': 'Səyahət söhbəti söndürülüb.',
|
||||
};
|
||||
export default assistant;
|
||||
@@ -114,5 +114,7 @@ const collab: TranslationStrings = {
|
||||
'collab.chat.attachImages': 'Şəkillər əlavə et',
|
||||
'collab.chat.attachedImage': 'Əlavə edilmiş şəkil',
|
||||
'collab.chat.closeImage': 'Şəkli bağla',
|
||||
'collab.chat.fromAssistant': 'köməkçidən',
|
||||
'collab.chat.fromAssistantHint': '{name} köməkçinin bu cavabını paylaşdı',
|
||||
};
|
||||
export default collab;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import admin from './admin';
|
||||
import airport from './airport';
|
||||
import assistant from './assistant';
|
||||
import atlas from './atlas';
|
||||
import backup from './backup';
|
||||
import budget from './budget';
|
||||
@@ -116,5 +117,6 @@ const locale = {
|
||||
...dawarich,
|
||||
...docsync,
|
||||
...ferratas,
|
||||
...assistant,
|
||||
};
|
||||
export default locale;
|
||||
|
||||
@@ -92,6 +92,7 @@ const oauth: TranslationStrings = {
|
||||
'oauth.authorize.loginButton': 'TREK-ə daxil ol',
|
||||
'oauth.authorize.requestLabel': 'İcazə sorğusu',
|
||||
'oauth.authorize.requestDescription': 'Bu tətbiq TREK hesabınıza giriş icazəsi istəyir.',
|
||||
'oauth.authorize.verifiedHost': 'Təsdiqlənmiş domen: {host}',
|
||||
'oauth.authorize.trustNote':
|
||||
'Yalnız etibar etdiyiniz tətbiqlərə giriş icazəsi verin. Məlumatlarınız serverinizdə qalır.',
|
||||
'oauth.authorize.selectScope': 'Ən azı bir icazə sahəsi seçin',
|
||||
|
||||
@@ -635,6 +635,9 @@ const admin: TranslationStrings = {
|
||||
'admin.addons.catalog.llm_parsing.name': 'Análise por IA',
|
||||
'admin.addons.catalog.llm_parsing.description':
|
||||
'Lê reservas que o analisador integrado não entende, usando um modelo de IA à sua escolha',
|
||||
'admin.addons.catalog.ai_assistant.name': 'Assistente de IA',
|
||||
'admin.addons.catalog.ai_assistant.description':
|
||||
'Um chat em cada viagem que responde perguntas e sugere mudanças que você confirma',
|
||||
'admin.addons.llm.vision.auto': 'Automático',
|
||||
'admin.addons.llm.vision.on': 'Sim',
|
||||
'admin.addons.llm.vision.off': 'Não',
|
||||
|
||||
@@ -0,0 +1,342 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistant: TranslationStrings = {
|
||||
'assistant.launcher.tooltip': 'Assistente',
|
||||
'assistant.launcher.ctrlKey': 'Ctrl', // same-as-en
|
||||
'assistant.picker.title': 'Qual viagem?',
|
||||
'assistant.picker.empty': 'Crie uma viagem primeiro, depois o assistente ajuda você a planejá-la.',
|
||||
'assistant.picker.now': 'Agora',
|
||||
'assistant.picker.next': 'Próxima',
|
||||
'assistant.window.title': 'Assistente',
|
||||
'assistant.window.log': 'Conversa com o assistente',
|
||||
'assistant.window.menu': 'Mais opções',
|
||||
'assistant.window.settings': 'Configurações de IA',
|
||||
'assistant.window.deleteThread': 'Excluir chat',
|
||||
'assistant.window.deleteConfirm': 'Excluir este chat? Isso não pode ser desfeito.',
|
||||
'assistant.setup.question': 'Com o que você quer conversar?',
|
||||
'assistant.setup.beta': 'Beta', // same-as-en
|
||||
'assistant.setup.chatgptNote': 'Uma chave de API é o caminho oficial. O login com o ChatGPT ainda está em beta.',
|
||||
'assistant.setup.keySubtitle': 'Anthropic, OpenAI, Gemini ou OpenRouter',
|
||||
'assistant.setup.adminOnly': 'Quem configura isso é o seu administrador.',
|
||||
'assistant.setup.forEveryone': 'Configurar para todos',
|
||||
'assistant.connect.title': 'Assistente de IA',
|
||||
'assistant.connect.description':
|
||||
'Converse com um assistente que conhece sua viagem e sugere mudanças que você confirma.',
|
||||
'assistant.connect.chatgpt': 'Entrar com o ChatGPT',
|
||||
'assistant.connect.chatgptHint': 'Usa o seu plano do ChatGPT. Sem chave.',
|
||||
'assistant.connect.chatgptStep1': 'Entre no ChatGPT na nova aba.',
|
||||
'assistant.connect.chatgptOpen': 'Abrir login do ChatGPT',
|
||||
'assistant.connect.chatgptStep2':
|
||||
'Depois, o navegador vai mostrar uma página de erro. Isso é esperado. Copie o endereço lá em cima e volte aqui.',
|
||||
'assistant.connect.chatgptPaste': 'Colar endereço',
|
||||
'assistant.connect.chatgptWrong': 'Esse não é o endereço certo. Ele começa com 127.0.0.1.',
|
||||
'assistant.connect.chatgptExpired': 'Demorou demais. Entre novamente.',
|
||||
'assistant.connect.chatgptOtherAccount': 'Esta é outra conta do ChatGPT. Remova a conexão primeiro.',
|
||||
'assistant.connect.key': 'Colar uma chave de API',
|
||||
'assistant.connect.keyPlaceholder': 'Cole sua chave aqui',
|
||||
'assistant.connect.detected': 'Detectado: {provider}',
|
||||
'assistant.connect.whichProvider': 'De qual provedor é esta chave?',
|
||||
'assistant.connect.testing': 'Verificando a conexão',
|
||||
'assistant.connect.ok': 'Conectado. Vamos lá.',
|
||||
'assistant.connect.openAssistant': 'Abrir assistente',
|
||||
'assistant.connect.badKey': 'Esta chave não funciona.',
|
||||
'assistant.connect.unknownModel': 'Este modelo não está disponível aqui.',
|
||||
'assistant.connect.getKey': 'Obter uma chave',
|
||||
'assistant.connect.hintAnthropic': 'Claude Max e Team incluem créditos de API.',
|
||||
'assistant.connect.hintOpenai': 'Crie uma chave no seu painel da OpenAI.',
|
||||
'assistant.connect.hintGemini': 'O AI Studio tem um nível gratuito com limites por minuto e por dia, no qual o Google pode usar o que você envia para melhorar seus produtos. Com o faturamento ativado, os limites são maiores e isso não se aplica.',
|
||||
'assistant.connect.hintOpenrouter': 'Entre com o OpenRouter, sem chave para copiar.',
|
||||
'assistant.connect.openrouter': 'Conectar OpenRouter',
|
||||
'assistant.connect.save': 'Conectar',
|
||||
'assistant.connect.model': 'Modelo',
|
||||
'assistant.connect.modelHint': 'Só aparecem modelos que podem usar ferramentas.',
|
||||
'assistant.connect.free': 'Nível gratuito disponível',
|
||||
'assistant.connect.paid': 'Pague pelo uso',
|
||||
'assistant.model.recommended': 'Recomendado',
|
||||
'assistant.model.free': 'Grátis',
|
||||
'assistant.connect.connected': 'Conectado a {provider}',
|
||||
'assistant.connect.providedByAdmin': 'Fornecido pelo seu administrador',
|
||||
'assistant.connect.disconnect': 'Desconectar',
|
||||
'assistant.connect.disconnectConfirm': 'Desconectar {provider}? Seus chats continuam salvos.',
|
||||
'assistant.connect.signOut': 'Sair',
|
||||
'assistant.connect.statusOk': 'Conectado',
|
||||
'assistant.connect.statusNeedsReauth': 'Login expirado',
|
||||
'assistant.connect.statusSignedOut': 'Desconectado',
|
||||
'assistant.connect.statusError': 'Inacessível',
|
||||
'assistant.connect.quota': '{used} de {count} mensagens usadas hoje',
|
||||
'assistant.connect.quota.one': '{used} de {count} mensagem usada hoje',
|
||||
'assistant.connect.claudeLink': 'Usa o Claude? Conecte no Claude',
|
||||
'assistant.privacy.what': 'O que é compartilhado?',
|
||||
'assistant.privacy.banner': 'Suas mensagens e os dados da viagem que o assistente lê vão para {provider}.',
|
||||
'assistant.privacy.ok': 'Entendi',
|
||||
'assistant.privacy.shared':
|
||||
'Suas mensagens e os detalhes da viagem que o assistente lê, como lugares, dias e reservas.',
|
||||
'assistant.privacy.notShared': 'Não sua senha, seus arquivos nem suas outras viagens.',
|
||||
'assistant.privacy.provider': '{provider} trata esses dados de acordo com seus próprios termos de privacidade.',
|
||||
'assistant.empty.title': 'O que vamos planejar?',
|
||||
'assistant.empty.readOnly':
|
||||
'Você só pode visualizar esta viagem, então posso responder perguntas, mas não alterar nada.',
|
||||
'assistant.suggest.planFirstDays': 'Planeje os primeiros dias para mim',
|
||||
'assistant.suggest.mustSee': 'O que não posso perder aqui?',
|
||||
'assistant.suggest.packingList': 'Faça uma lista de bagagem para esta viagem',
|
||||
'assistant.suggest.emptyDays': 'Quais dias ainda estão vazios?',
|
||||
'assistant.suggest.rainPlan': 'O que podemos fazer se chover?',
|
||||
'assistant.suggest.today': 'O que tem para hoje?',
|
||||
'assistant.suggest.eatNearby': 'Encontre um lugar para comer por perto',
|
||||
'assistant.suggest.weather': 'Como está a previsão do tempo?',
|
||||
'assistant.suggest.openTodos': 'O que ainda está pendente na lista de tarefas?',
|
||||
'assistant.suggest.settleUp': 'Quem deve para quem?',
|
||||
'assistant.suggest.recap': 'Resuma nossa viagem',
|
||||
'assistant.suggest.nearPlace': 'O que mais tem perto de {place}?',
|
||||
'assistant.suggest.placeTips': 'Dicas para visitar {place}',
|
||||
'assistant.suggest.planDay': 'Planeje {day} para mim',
|
||||
'assistant.suggest.dayTooFull': '{day} está cheio demais?',
|
||||
'assistant.thread.scrollDown': 'Ir para a última mensagem',
|
||||
'assistant.attach.button': 'Anexar reserva',
|
||||
'assistant.attach.drop': 'Solte aqui e eu leio a reserva',
|
||||
'assistant.attach.unsupported': 'Não consigo ler este tipo de arquivo. Tente PDF, e-mail, imagem ou cartão da carteira.',
|
||||
'assistant.attach.tooLarge': 'Este arquivo tem mais de 10 MB.',
|
||||
'assistant.composer.placeholder': 'Pergunte qualquer coisa sobre {trip}',
|
||||
'assistant.composer.send': 'Enviar',
|
||||
'assistant.composer.stop': 'Parar',
|
||||
'assistant.composer.decideFirst': 'Aplique ou descarte as mudanças acima primeiro.',
|
||||
'assistant.context.ask': 'Perguntar',
|
||||
'assistant.context.remove': 'Remover contexto',
|
||||
'assistant.step.reading': 'Lendo seu plano',
|
||||
'assistant.step.searching': 'Procurando lugares',
|
||||
'assistant.step.details': 'Buscando detalhes',
|
||||
'assistant.step.nearby': 'Procurando por perto',
|
||||
'assistant.step.categories': 'Verificando categorias',
|
||||
'assistant.step.weather': 'Verificando o tempo',
|
||||
'assistant.step.todos': 'Lendo a lista de tarefas',
|
||||
'assistant.step.costs': 'Verificando os custos',
|
||||
'assistant.step.help': 'Consultando a ajuda',
|
||||
'assistant.step.readingBooking': 'Lendo a reserva',
|
||||
'assistant.step.working': 'Trabalhando nisso',
|
||||
'assistant.step.collapsed': '{count} etapas',
|
||||
'assistant.step.collapsed.one': '{count} etapa',
|
||||
'assistant.change.title': 'Alterações sugeridas',
|
||||
'assistant.change.doneTitle': 'Pronto',
|
||||
'assistant.change.apply': 'Aplicar',
|
||||
'assistant.change.applyN': 'Aplicar {count}',
|
||||
'assistant.change.applyN.one': 'Aplicar {count}',
|
||||
'assistant.change.discard': 'Descartar',
|
||||
'assistant.change.include': 'Incluir esta mudança',
|
||||
'assistant.change.applied': 'Aplicado',
|
||||
'assistant.change.discarded': 'Descartado',
|
||||
'assistant.change.failed': 'Não foi possível aplicar.',
|
||||
'assistant.change.applying': 'Aplicando',
|
||||
'assistant.change.expired': 'Não está mais disponível',
|
||||
'assistant.change.undo': 'Desfazer',
|
||||
'assistant.change.undone': 'Desfeito',
|
||||
'assistant.change.undoStale': 'Foi alterado nesse meio-tempo. Ajuste manualmente.',
|
||||
'assistant.change.undoFailed': 'Não foi possível desfazer.',
|
||||
'assistant.change.showOnMap': 'Mostrar no mapa',
|
||||
'assistant.change.empty': 'vazio',
|
||||
'assistant.change.pending': '{count} mudanças esperam por você',
|
||||
'assistant.change.pending.one': '{count} mudança espera por você',
|
||||
'assistant.change.verb.addPlace': 'Adicionar lugar',
|
||||
'assistant.change.verb.addPlaceToDay': 'Adicionar lugar ao dia',
|
||||
'assistant.change.verb.updatePlace': 'Alterar lugar',
|
||||
'assistant.change.verb.assignPlace': 'Planejar lugar em um dia',
|
||||
'assistant.change.verb.moveAssignment': 'Mover para outro dia',
|
||||
'assistant.change.verb.removeFromDay': 'Tirar do dia',
|
||||
'assistant.change.verb.addDayNote': 'Adicionar nota',
|
||||
'assistant.change.verb.updateDayNote': 'Alterar nota',
|
||||
'assistant.change.verb.addPackingItem': 'Adicionar à lista de bagagem',
|
||||
'assistant.change.verb.updatePackingItem': 'Alterar item da bagagem',
|
||||
'assistant.change.verb.addTodo': 'Adicionar tarefa',
|
||||
'assistant.change.verb.addBudgetItem': 'Adicionar despesa',
|
||||
'assistant.change.verb.addReservation': 'Adicionar reserva',
|
||||
'assistant.change.verb.importBooking': 'Adicionar reserva a partir de arquivo',
|
||||
'assistant.change.label.name': 'Nome',
|
||||
'assistant.change.label.description': 'Descrição',
|
||||
'assistant.change.label.address': 'Endereço',
|
||||
'assistant.change.label.coordinates': 'Local no mapa',
|
||||
'assistant.change.label.category': 'Categoria',
|
||||
'assistant.change.label.day': 'Dia',
|
||||
'assistant.change.label.time': 'Horário',
|
||||
'assistant.change.label.endTime': 'Até',
|
||||
'assistant.change.label.notes': 'Notas',
|
||||
'assistant.change.label.website': 'Site',
|
||||
'assistant.change.label.phone': 'Telefone',
|
||||
'assistant.change.label.email': 'E-mail',
|
||||
'assistant.change.label.openingHours': 'Horário de funcionamento',
|
||||
'assistant.change.label.price': 'Preço',
|
||||
'assistant.change.label.currency': 'Moeda',
|
||||
'assistant.change.label.stopType': 'Tipo de parada',
|
||||
'assistant.change.label.text': 'Texto',
|
||||
'assistant.change.label.icon': 'Ícone',
|
||||
'assistant.change.label.color': 'Cor',
|
||||
'assistant.change.label.position': 'Ordem',
|
||||
'assistant.change.label.quantity': 'Quantidade',
|
||||
'assistant.change.label.weight': 'Peso',
|
||||
'assistant.change.label.bag': 'Mala',
|
||||
'assistant.change.label.packed': 'Na mala',
|
||||
'assistant.change.label.dueDate': 'Prazo',
|
||||
'assistant.change.label.priority': 'Prioridade',
|
||||
'assistant.change.label.assignee': 'Atribuído a',
|
||||
'assistant.change.label.type': 'Tipo',
|
||||
'assistant.change.label.location': 'Local',
|
||||
'assistant.change.label.link': 'Link', // same-as-en
|
||||
'assistant.change.label.confirmation': 'Número de confirmação',
|
||||
'assistant.change.label.checkIn': 'Check-in', // same-as-en
|
||||
'assistant.change.label.checkOut': 'Check-out', // same-as-en
|
||||
'assistant.change.label.stay': 'Estadia',
|
||||
'assistant.change.label.members': 'Dividido entre',
|
||||
'assistant.change.label.route': 'Trajeto',
|
||||
'assistant.history.open': 'Chats recentes',
|
||||
'assistant.history.new': 'Novo chat',
|
||||
'assistant.history.empty': 'Nenhum chat ainda.',
|
||||
'assistant.history.retention': 'Os chats são excluídos após {count} dias.',
|
||||
'assistant.history.retention.one': 'Os chats são excluídos após {count} dia.',
|
||||
'assistant.history.forever': 'Os chats ficam salvos até você excluí-los.',
|
||||
'assistant.error.notConnected': 'O assistente ainda não está conectado.',
|
||||
'assistant.error.busy': 'Já está em execução em outra janela.',
|
||||
'assistant.error.dailyLimit': 'Por hoje é só. Até amanhã.',
|
||||
'assistant.error.rateLimited': 'Mais devagar. Tente de novo em um minuto.',
|
||||
'assistant.error.needsReauth': 'Seu login no {provider} expirou.',
|
||||
'assistant.error.usageLimit': 'Seu uso do ChatGPT acabou.',
|
||||
'assistant.error.notEligible': 'Esta conta do ChatGPT não pode ser usada aqui.',
|
||||
'assistant.error.invalidKey': 'Sua chave do {provider} não funciona mais.',
|
||||
'assistant.error.providerDown': '{provider} não está respondendo no momento.',
|
||||
'assistant.error.maxSteps': 'Foi muita coisa de uma vez. Posso continuar?',
|
||||
'assistant.error.offline': 'Sem conexão. O assistente precisa de internet.',
|
||||
'assistant.error.noBookingsFound': 'Nenhuma reserva encontrada neste arquivo.',
|
||||
'assistant.error.importUnavailable': 'A leitura de arquivos de reserva não está disponível aqui.',
|
||||
'assistant.error.generic': 'Algo deu errado.',
|
||||
'assistant.errorAction.retry': 'Tentar novamente',
|
||||
'assistant.errorAction.continueHere': 'Continuar aqui',
|
||||
'assistant.errorAction.continue': 'Continuar',
|
||||
'assistant.errorAction.signIn': 'Entrar novamente',
|
||||
'assistant.errorAction.viewUsage': 'Ver uso',
|
||||
'assistant.errorAction.otherProvider': 'Escolher outro provedor',
|
||||
'assistant.errorAction.replaceKey': 'Substituir chave',
|
||||
'assistant.errorAction.connectOwn': 'Conectar seu próprio provedor',
|
||||
'assistant.admin.policy': 'Quem paga',
|
||||
'assistant.admin.policy.instance': 'Eu pago para todos',
|
||||
'assistant.admin.policy.instanceOrUser': 'Eu pago, conta própria permitida',
|
||||
'assistant.admin.policy.user': 'Cada um usa a própria conta',
|
||||
'assistant.admin.policy.instanceHint': 'Todos usam a conexão abaixo e não podem adicionar a sua.',
|
||||
'assistant.admin.policy.instanceOrUserHint': 'Todos usam a conexão abaixo, a menos que conectem a sua.',
|
||||
'assistant.admin.policy.userHint': 'Cada um conecta seu próprio provedor. Nada é cobrado de você.',
|
||||
'assistant.admin.connection': 'Conexão para todos',
|
||||
'assistant.admin.connectionNone': 'Nenhuma conexão ainda.',
|
||||
'assistant.admin.connectionRemove': 'Remover conexão',
|
||||
'assistant.admin.custom': 'Servidor próprio',
|
||||
'assistant.admin.customHint': 'Qualquer servidor compatível com OpenAI, por exemplo Ollama.',
|
||||
'assistant.admin.baseUrl': 'Endereço do servidor',
|
||||
'assistant.admin.apiKeyOptional': 'Chave de API (opcional)',
|
||||
'assistant.admin.prefilled': 'Copiado de Análise por IA.',
|
||||
'assistant.admin.dailyLimit': 'Mensagens por pessoa por dia',
|
||||
'assistant.admin.dailyLimitHint': 'Só conta enquanto você paga.',
|
||||
'assistant.admin.retention': 'Manter chats',
|
||||
'assistant.admin.retentionDays': '{count} dias',
|
||||
'assistant.admin.retentionDays.one': '{count} dia',
|
||||
'assistant.admin.retentionForever': 'Para sempre',
|
||||
'assistant.claude.title': 'Conectar com o Claude',
|
||||
'assistant.claude.description': 'Use o TREK direto no Claude: adicione como conector, entre e pronto.',
|
||||
'assistant.claude.open': 'Abrir no Claude',
|
||||
'assistant.claude.scan': 'Ou escaneie com o celular',
|
||||
'assistant.claude.copyUrl': 'Copiar endereço',
|
||||
'assistant.claude.qrAlt': 'QR code para adicionar o TREK no Claude',
|
||||
'assistant.claude.notPublic': 'O Claude só consegue acessar o TREK se ele estiver disponível via https na internet.',
|
||||
'assistant.mobile.tile': 'Pergunte ao TREK',
|
||||
'assistant.mobile.tileHint': 'Planeje com o assistente',
|
||||
'assistant.mobile.backToChat': 'Voltar ao chat',
|
||||
'assistant.step.nearbyAt': 'Buscando perto de {place}',
|
||||
'assistant.step.searchingFor': 'Procurando {query}',
|
||||
'assistant.step.weatherAt': 'Verificando o tempo em {place}',
|
||||
'assistant.change.timeline.show': 'Mostrar roteiro',
|
||||
'assistant.change.timeline.hide': 'Ocultar roteiro',
|
||||
'assistant.change.timeline.new': 'Novo',
|
||||
'assistant.change.timeline.travel': '≈ {count} min de trajeto',
|
||||
'assistant.change.timeline.travel.one': '≈ {count} min de trajeto',
|
||||
'assistant.change.timeline.gap': '{count} min livres',
|
||||
'assistant.change.timeline.gap.one': '{count} min livre',
|
||||
'assistant.change.timeline.overlap': 'Coincide com {place}',
|
||||
'assistant.change.timeline.moreDays': 'Mais {count} dias',
|
||||
'assistant.change.timeline.moreDays.one': 'Mais {count} dia',
|
||||
'assistant.change.viewOnMap': 'Ver no mapa',
|
||||
'assistant.change.before': 'Antes',
|
||||
'assistant.change.after': 'Depois',
|
||||
'assistant.change.compare': 'Comparar no mapa',
|
||||
'assistant.change.undoAll': 'Desfazer tudo',
|
||||
'assistant.change.undoneAll': '{count} mudanças desfeitas',
|
||||
'assistant.change.undoneAll.one': '{count} mudança desfeita',
|
||||
'assistant.change.undoAllPartial': 'Desfeitas: {done}; alteradas nesse meio-tempo: {stale}',
|
||||
'assistant.change.undoAllFailed': 'Não foi possível desfazer {count} mudanças',
|
||||
'assistant.change.undoAllFailed.one': 'Não foi possível desfazer {count} mudança',
|
||||
'assistant.place.strip': 'Lugares encontrados',
|
||||
'assistant.place.addTo': 'Adicionar a {day}',
|
||||
'assistant.place.addToShort': '+ Dia {n}',
|
||||
'assistant.place.pickDay': 'Escolher dia',
|
||||
'assistant.place.suggested': 'Sugerido',
|
||||
'assistant.place.inTrip': 'Já está na sua viagem',
|
||||
'assistant.place.showOnMap': 'No mapa',
|
||||
'assistant.place.openNow': 'Aberto agora',
|
||||
'assistant.place.closed': 'Fechado',
|
||||
'assistant.place.fromPrev': 'A {distance} da parada anterior',
|
||||
'assistant.place.more': 'Mais {count}',
|
||||
'assistant.place.more.one': 'Mais {count}',
|
||||
'assistant.mention.days': 'Dias',
|
||||
'assistant.mention.places': 'Lugares',
|
||||
'assistant.mention.bookings': 'Reservas',
|
||||
'assistant.mention.none': 'Nada encontrado',
|
||||
'assistant.mention.hint': 'Digite @ para mencionar dias, lugares e reservas.',
|
||||
'assistant.attach.dropPlace': 'Solte o lugar aqui',
|
||||
'assistant.weather.strip': 'Tempo para {count} dias',
|
||||
'assistant.weather.strip.one': 'Tempo para {count} dia',
|
||||
'assistant.weather.rain': '{pct}% de chuva',
|
||||
'assistant.settings.followMap': 'O mapa acompanha o assistente',
|
||||
'assistant.settings.followMapHint': 'O mapa se move para onde o assistente pesquisa e sugere lugares.',
|
||||
'assistant.error.decideFirst': 'Primeiro decida sobre as sugestões em aberto.',
|
||||
'assistant.error.providerLimit': 'Limite atingido em {provider}. Tente novamente mais tarde.',
|
||||
'assistant.hint.title': 'Vale conferir',
|
||||
'assistant.hint.dismiss': 'Ocultar este aviso',
|
||||
'assistant.hint.more': 'Mais {count}',
|
||||
'assistant.hint.more.one': 'Mais {count}',
|
||||
'assistant.hint.overlap': '{a} e {b} se sobrepõem em {day}',
|
||||
'assistant.hint.overlap.prompt': 'Em {day}, {a} e {b} se sobrepõem. Como resolvemos isso?',
|
||||
'assistant.hint.closed': '{place} está fechado em {day}',
|
||||
'assistant.hint.closed.prompt': '{place} está fechado em {day}. O que fazemos no lugar?',
|
||||
'assistant.hint.opensLate': '{place} só abre às {time} em {day}',
|
||||
'assistant.hint.closesEarly': '{place} fecha já às {time} em {day}',
|
||||
'assistant.hint.outsideHours.prompt':
|
||||
'Nossa visita a {place} em {day} não bate com o horário de funcionamento. Qual a melhor forma de remarcar?',
|
||||
'assistant.hint.gap': '{day}: {count} horas livres a partir das {from}',
|
||||
'assistant.hint.gap.one': '{day}: {count} hora livre a partir das {from}',
|
||||
'assistant.hint.gap.prompt': 'Não há nada planejado em {day} das {from} às {to}. O que caberia aí?',
|
||||
'assistant.hint.longLeg': '{from} e {to} ficam a {distance} em linha reta',
|
||||
'assistant.hint.longLeg.prompt': 'É longe demais ir de {from} a {to} em {day}? Qual o melhor jeito de chegar?',
|
||||
'assistant.hint.tightLeg': 'Só {count} minutos entre {from} e {to}',
|
||||
'assistant.hint.tightLeg.one': 'Só {count} minuto entre {from} e {to}',
|
||||
'assistant.hint.tightLeg.prompt': 'Dá tempo em {day} de ir de {from} a {to}? O que devemos mudar?',
|
||||
'assistant.hint.noStay': 'Ainda sem hospedagem para {count} noites a partir de {day}',
|
||||
'assistant.hint.noStay.one': 'Ainda sem hospedagem para {count} noite a partir de {day}',
|
||||
'assistant.hint.noStay.prompt': 'Ainda falta hospedagem a partir de {day}. O que você sugere?',
|
||||
'assistant.hint.unplacedBooking': '{title} não está em nenhum dia',
|
||||
'assistant.hint.unplacedBookingDay': '{title} provavelmente é de {day}',
|
||||
'assistant.hint.unplacedBookings': '{count} reservas não estão em nenhum dia',
|
||||
'assistant.hint.unplacedBookings.one': '{count} reserva não está em nenhum dia',
|
||||
'assistant.hint.unplacedBooking.prompt': 'Quais reservas não estão em nenhum dia, e onde elas entram?',
|
||||
'assistant.hint.menuOn': 'Mostrar avisos',
|
||||
'assistant.hint.menuOff': 'Não mostrar avisos',
|
||||
'assistant.launcher.withHints': 'Assistente, {count} avisos',
|
||||
'assistant.launcher.withHints.one': 'Assistente, {count} aviso',
|
||||
'assistant.mobile.tileHints': '{count} pontos para conferir no seu plano',
|
||||
'assistant.mobile.tileHints.one': '{count} ponto para conferir no seu plano',
|
||||
'assistant.share.button': 'Compartilhar no chat da viagem',
|
||||
'assistant.share.title': 'Compartilhar no chat da viagem',
|
||||
'assistant.share.who': 'Todos na viagem verão esta mensagem, marcada como do assistente.',
|
||||
'assistant.share.includeChanges': 'Incluir as mudanças aplicadas',
|
||||
'assistant.share.changesHeading': 'Aplicado:',
|
||||
'assistant.share.truncated': 'Encurtado para {count} caracteres',
|
||||
'assistant.share.truncated.one': 'Encurtado para {count} caractere',
|
||||
'assistant.share.send': 'Compartilhar',
|
||||
'assistant.share.done': 'Compartilhado no chat da viagem',
|
||||
'assistant.share.shared': 'Compartilhado',
|
||||
'assistant.share.failed': 'Não deu para compartilhar. Tente de novo em instantes.',
|
||||
'assistant.share.chatOff': 'O chat da viagem está desativado.',
|
||||
};
|
||||
export default assistant;
|
||||
@@ -106,5 +106,7 @@ const collab: TranslationStrings = {
|
||||
'collab.chat.attachImages': 'Anexar imagens',
|
||||
'collab.chat.attachedImage': 'Imagem anexada',
|
||||
'collab.chat.closeImage': 'Fechar imagem',
|
||||
'collab.chat.fromAssistant': 'do assistente',
|
||||
'collab.chat.fromAssistantHint': '{name} compartilhou esta resposta do assistente',
|
||||
};
|
||||
export default collab;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import admin from './admin';
|
||||
import airport from './airport';
|
||||
import assistant from './assistant';
|
||||
import atlas from './atlas';
|
||||
import backup from './backup';
|
||||
import budget from './budget';
|
||||
@@ -116,5 +117,6 @@ const locale = {
|
||||
...dawarich,
|
||||
...docsync,
|
||||
...ferratas,
|
||||
...assistant,
|
||||
};
|
||||
export default locale;
|
||||
|
||||
@@ -84,6 +84,7 @@ const oauth: TranslationStrings = {
|
||||
'oauth.authorize.loginButton': 'Sign in to TREK', // en-fallback
|
||||
'oauth.authorize.requestLabel': 'Authorization Request', // en-fallback
|
||||
'oauth.authorize.requestDescription': 'This application is requesting access to your TREK account.', // en-fallback
|
||||
'oauth.authorize.verifiedHost': 'Domínio verificado: {host}',
|
||||
'oauth.authorize.trustNote': 'Only grant access to applications you trust. Your data stays on your server.', // en-fallback
|
||||
'oauth.authorize.selectScope': 'Select at least one scope', // en-fallback
|
||||
'oauth.authorize.approveScopes': 'Aprovar ({count} escopos)',
|
||||
|
||||
@@ -243,6 +243,9 @@ const admin: TranslationStrings = {
|
||||
'admin.addons.catalog.llm_parsing.name': 'Anàlisi amb IA',
|
||||
'admin.addons.catalog.llm_parsing.description':
|
||||
'Llegeix reserves que l’analitzador integrat no entén, amb un model d’IA que triïs',
|
||||
'admin.addons.catalog.ai_assistant.name': 'Assistent d’IA',
|
||||
'admin.addons.catalog.ai_assistant.description':
|
||||
'Un xat a cada viatge que respon preguntes i proposa canvis que tu confirmes',
|
||||
'admin.addons.llm.vision.auto': 'Automàtic',
|
||||
'admin.addons.llm.vision.on': 'Sí',
|
||||
'admin.addons.llm.vision.off': 'No',
|
||||
|
||||
@@ -0,0 +1,345 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistant: TranslationStrings = {
|
||||
'assistant.launcher.tooltip': 'Assistent',
|
||||
'assistant.launcher.ctrlKey': 'Ctrl', // same-as-en
|
||||
'assistant.picker.title': 'Quin viatge?',
|
||||
'assistant.picker.empty': 'Crea primer un viatge i l’assistent t’ajudarà a planificar-lo.',
|
||||
'assistant.picker.now': 'Ara',
|
||||
'assistant.picker.next': 'Següent',
|
||||
'assistant.window.title': 'Assistent',
|
||||
'assistant.window.log': 'Conversa amb l’assistent',
|
||||
'assistant.window.menu': 'Més opcions',
|
||||
'assistant.window.settings': 'Configuració d’IA',
|
||||
'assistant.window.deleteThread': 'Elimina el xat',
|
||||
'assistant.window.deleteConfirm': 'Vols eliminar aquest xat? No es pot desfer.',
|
||||
'assistant.setup.question': 'Amb què vols xatejar?',
|
||||
'assistant.setup.beta': 'Beta', // same-as-en
|
||||
'assistant.setup.chatgptNote': 'Una clau API és la via oficial. L’inici de sessió amb ChatGPT encara és en beta.',
|
||||
'assistant.setup.keySubtitle': 'Anthropic, OpenAI, Gemini o OpenRouter',
|
||||
'assistant.setup.adminOnly': 'Ho configura el teu administrador.',
|
||||
'assistant.setup.forEveryone': 'Configura-ho per a tothom',
|
||||
'assistant.connect.title': 'Assistent d’IA',
|
||||
'assistant.connect.description':
|
||||
'Xateja amb un assistent que coneix el teu viatge i proposa canvis que tu confirmes.',
|
||||
'assistant.connect.chatgpt': 'Inicia sessió amb ChatGPT',
|
||||
'assistant.connect.chatgptHint': 'Fa servir el teu pla de ChatGPT. No cal cap clau.',
|
||||
'assistant.connect.chatgptStep1': 'Inicia sessió a ChatGPT a la pestanya nova.',
|
||||
'assistant.connect.chatgptOpen': 'Obre l’inici de sessió de ChatGPT',
|
||||
'assistant.connect.chatgptStep2':
|
||||
'Després el navegador mostrarà una pàgina d’error. És normal. Copia l’adreça de dalt de tot i torna aquí.',
|
||||
'assistant.connect.chatgptPaste': 'Enganxa l’adreça',
|
||||
'assistant.connect.chatgptWrong': 'Aquesta no és l’adreça correcta. Comença per 127.0.0.1.',
|
||||
'assistant.connect.chatgptExpired': 'Ha trigat massa. Torna a iniciar sessió.',
|
||||
'assistant.connect.chatgptOtherAccount': 'Aquest és un altre compte de ChatGPT. Elimina primer la connexió.',
|
||||
'assistant.connect.key': 'Enganxa una clau API',
|
||||
'assistant.connect.keyPlaceholder': 'Enganxa la teva clau aquí',
|
||||
'assistant.connect.detected': 'Detectat: {provider}',
|
||||
'assistant.connect.whichProvider': 'De quin proveïdor és aquesta clau?',
|
||||
'assistant.connect.testing': 'Comprovant la connexió',
|
||||
'assistant.connect.ok': 'Connectat. Som-hi.',
|
||||
'assistant.connect.openAssistant': 'Obre l’assistent',
|
||||
'assistant.connect.badKey': 'Aquesta clau no funciona.',
|
||||
'assistant.connect.unknownModel': 'Aquest model no s’ofereix aquí.',
|
||||
'assistant.connect.getKey': 'Obtén una clau',
|
||||
'assistant.connect.hintAnthropic': 'Claude Max i Team inclouen crèdits d’API.',
|
||||
'assistant.connect.hintOpenai': 'Crea una clau al teu tauler d’OpenAI.',
|
||||
'assistant.connect.hintGemini': 'L’AI Studio té un nivell gratuït amb límits per minut i per dia, en què Google pot fer servir el que envies per millorar els seus productes. Amb la facturació activada els límits són més alts i això no s’aplica.',
|
||||
'assistant.connect.hintOpenrouter': 'Inicia sessió amb OpenRouter, sense clau per copiar.',
|
||||
'assistant.connect.openrouter': 'Connecta OpenRouter',
|
||||
'assistant.connect.save': 'Connecta',
|
||||
'assistant.connect.model': 'Model', // same-as-en
|
||||
'assistant.connect.modelHint': 'Només es mostren els models que poden fer servir eines.',
|
||||
'assistant.connect.free': 'Nivell gratuït disponible',
|
||||
'assistant.connect.paid': 'Pagament per ús',
|
||||
'assistant.model.recommended': 'Recomanat',
|
||||
'assistant.model.free': 'Gratuït',
|
||||
'assistant.connect.connected': 'Connectat a {provider}',
|
||||
'assistant.connect.providedByAdmin': 'Proporcionat pel teu administrador',
|
||||
'assistant.connect.disconnect': 'Desconnecta',
|
||||
'assistant.connect.disconnectConfirm': 'Vols desconnectar {provider}? Els teus xats es conserven.',
|
||||
'assistant.connect.signOut': 'Tanca la sessió',
|
||||
'assistant.connect.statusOk': 'Connectat',
|
||||
'assistant.connect.statusNeedsReauth': 'Sessió caducada',
|
||||
'assistant.connect.statusSignedOut': 'Sessió tancada',
|
||||
'assistant.connect.statusError': 'No accessible',
|
||||
'assistant.connect.quota': '{used} de {count} missatges utilitzats avui',
|
||||
'assistant.connect.quota.one': '{used} de {count} missatge utilitzat avui',
|
||||
'assistant.connect.claudeLink': 'Fas servir Claude? Connecta’t a Claude',
|
||||
'assistant.privacy.what': 'Què es comparteix?',
|
||||
'assistant.privacy.banner':
|
||||
'Els teus missatges i les dades del viatge que llegeix l’assistent s’envien a {provider}.',
|
||||
'assistant.privacy.ok': 'Entesos',
|
||||
'assistant.privacy.shared':
|
||||
'Els teus missatges i els detalls del viatge que llegeix l’assistent, com llocs, dies i reserves.',
|
||||
'assistant.privacy.notShared': 'Ni la teva contrasenya, ni els teus fitxers, ni els teus altres viatges.',
|
||||
'assistant.privacy.provider': '{provider} les tracta segons les seves pròpies condicions de privadesa.',
|
||||
'assistant.empty.title': 'Què planifiquem?',
|
||||
'assistant.empty.readOnly': 'Només pots veure aquest viatge, així que puc respondre preguntes però no canviar res.',
|
||||
'assistant.suggest.planFirstDays': 'Planifica’m els primers dies',
|
||||
'assistant.suggest.mustSee': 'Què no m’hauria de perdre aquí?',
|
||||
'assistant.suggest.packingList': 'Fes una llista d’equipatge per a aquest viatge',
|
||||
'assistant.suggest.emptyDays': 'Quins dies encara són buits?',
|
||||
'assistant.suggest.rainPlan': 'Què podem fer si plou?',
|
||||
'assistant.suggest.today': 'Què hi ha avui?',
|
||||
'assistant.suggest.eatNearby': 'Troba un lloc per menjar a prop',
|
||||
'assistant.suggest.weather': 'Quin temps farà?',
|
||||
'assistant.suggest.openTodos': 'Què queda pendent a la llista de tasques?',
|
||||
'assistant.suggest.settleUp': 'Qui deu a qui?',
|
||||
'assistant.suggest.recap': 'Resumeix el nostre viatge',
|
||||
'assistant.suggest.nearPlace': 'Què més hi ha a prop de {place}?',
|
||||
'assistant.suggest.placeTips': 'Consells per visitar {place}',
|
||||
'assistant.suggest.planDay': 'Planifica’m {day}',
|
||||
'assistant.suggest.dayTooFull': '{day} està massa ple?',
|
||||
'assistant.thread.scrollDown': 'Ves a l’últim missatge',
|
||||
'assistant.attach.button': 'Adjunta una reserva',
|
||||
'assistant.attach.drop': 'Deixa-ho aquí i llegiré la reserva',
|
||||
'assistant.attach.unsupported': 'No puc llegir aquest tipus de fitxer. Prova amb un PDF, un correu, una imatge o una targeta de cartera.',
|
||||
'assistant.attach.tooLarge': 'Aquest fitxer fa més de 10 MB.',
|
||||
'assistant.composer.placeholder': 'Pregunta el que vulguis sobre {trip}',
|
||||
'assistant.composer.send': 'Envia',
|
||||
'assistant.composer.stop': 'Atura',
|
||||
'assistant.composer.decideFirst': 'Aplica o descarta primer els canvis de dalt.',
|
||||
'assistant.context.ask': 'Pregunta',
|
||||
'assistant.context.remove': 'Treu el context',
|
||||
'assistant.step.reading': 'Llegint el teu pla',
|
||||
'assistant.step.searching': 'Cercant llocs',
|
||||
'assistant.step.details': 'Consultant detalls',
|
||||
'assistant.step.nearby': 'Cercant a prop',
|
||||
'assistant.step.categories': 'Revisant categories',
|
||||
'assistant.step.weather': 'Consultant el temps',
|
||||
'assistant.step.todos': 'Llegint la llista de tasques',
|
||||
'assistant.step.costs': 'Revisant les despeses',
|
||||
'assistant.step.help': 'Cercant a l’ajuda',
|
||||
'assistant.step.readingBooking': 'Llegint la reserva',
|
||||
'assistant.step.working': 'Hi estic treballant',
|
||||
'assistant.step.collapsed': '{count} passos',
|
||||
'assistant.step.collapsed.one': '{count} pas',
|
||||
'assistant.change.title': 'Canvis suggerits',
|
||||
'assistant.change.doneTitle': 'Fet',
|
||||
'assistant.change.apply': 'Aplica',
|
||||
'assistant.change.applyN': 'Aplica’n {count}',
|
||||
'assistant.change.applyN.one': 'Aplica’n {count}',
|
||||
'assistant.change.discard': 'Descarta',
|
||||
'assistant.change.include': 'Inclou aquest canvi',
|
||||
'assistant.change.applied': 'Aplicat',
|
||||
'assistant.change.discarded': 'Descartat',
|
||||
'assistant.change.failed': 'No s’ha pogut aplicar.',
|
||||
'assistant.change.applying': 'Aplicant',
|
||||
'assistant.change.expired': 'Ja no està disponible',
|
||||
'assistant.change.undo': 'Desfés',
|
||||
'assistant.change.undone': 'Desfet',
|
||||
'assistant.change.undoStale': 'Ha canviat mentrestant. Ajusta-ho a mà.',
|
||||
'assistant.change.undoFailed': 'No s’ha pogut desfer.',
|
||||
'assistant.change.showOnMap': 'Mostra al mapa',
|
||||
'assistant.change.empty': 'buit',
|
||||
'assistant.change.pending': 'Tens {count} canvis pendents',
|
||||
'assistant.change.pending.one': 'Tens {count} canvi pendent',
|
||||
'assistant.change.verb.addPlace': 'Afegeix un lloc',
|
||||
'assistant.change.verb.addPlaceToDay': 'Afegeix un lloc al dia',
|
||||
'assistant.change.verb.updatePlace': 'Canvia el lloc',
|
||||
'assistant.change.verb.assignPlace': 'Planifica el lloc en un dia',
|
||||
'assistant.change.verb.moveAssignment': 'Mou a un altre dia',
|
||||
'assistant.change.verb.removeFromDay': 'Treure del dia',
|
||||
'assistant.change.verb.addDayNote': 'Afegeix una nota',
|
||||
'assistant.change.verb.updateDayNote': 'Canvia la nota',
|
||||
'assistant.change.verb.addPackingItem': 'Afegeix a la llista d’equipatge',
|
||||
'assistant.change.verb.updatePackingItem': 'Canvia l’article d’equipatge',
|
||||
'assistant.change.verb.addTodo': 'Afegeix una tasca',
|
||||
'assistant.change.verb.addBudgetItem': 'Afegeix una despesa',
|
||||
'assistant.change.verb.addReservation': 'Afegeix una reserva',
|
||||
'assistant.change.verb.importBooking': 'Afegeix una reserva des d’un fitxer',
|
||||
'assistant.change.label.name': 'Nom',
|
||||
'assistant.change.label.description': 'Descripció',
|
||||
'assistant.change.label.address': 'Adreça',
|
||||
'assistant.change.label.coordinates': 'Ubicació al mapa',
|
||||
'assistant.change.label.category': 'Categoria',
|
||||
'assistant.change.label.day': 'Dia',
|
||||
'assistant.change.label.time': 'Hora',
|
||||
'assistant.change.label.endTime': 'Fins a',
|
||||
'assistant.change.label.notes': 'Notes', // same-as-en
|
||||
'assistant.change.label.website': 'Lloc web',
|
||||
'assistant.change.label.phone': 'Telèfon',
|
||||
'assistant.change.label.email': 'Correu electrònic',
|
||||
'assistant.change.label.openingHours': 'Horari d’obertura',
|
||||
'assistant.change.label.price': 'Preu',
|
||||
'assistant.change.label.currency': 'Moneda',
|
||||
'assistant.change.label.stopType': 'Tipus de parada',
|
||||
'assistant.change.label.text': 'Text', // same-as-en
|
||||
'assistant.change.label.icon': 'Icona',
|
||||
'assistant.change.label.color': 'Color',
|
||||
'assistant.change.label.position': 'Ordre',
|
||||
'assistant.change.label.quantity': 'Quantitat',
|
||||
'assistant.change.label.weight': 'Pes',
|
||||
'assistant.change.label.bag': 'Bossa',
|
||||
'assistant.change.label.packed': 'Preparat',
|
||||
'assistant.change.label.dueDate': 'Venciment',
|
||||
'assistant.change.label.priority': 'Prioritat',
|
||||
'assistant.change.label.assignee': 'Assignat a',
|
||||
'assistant.change.label.type': 'Tipus',
|
||||
'assistant.change.label.location': 'Ubicació',
|
||||
'assistant.change.label.link': 'Enllaç',
|
||||
'assistant.change.label.confirmation': 'Número de confirmació',
|
||||
'assistant.change.label.checkIn': 'Entrada',
|
||||
'assistant.change.label.checkOut': 'Sortida',
|
||||
'assistant.change.label.stay': 'Estada',
|
||||
'assistant.change.label.members': 'Repartit entre',
|
||||
'assistant.change.label.route': 'Trajecte',
|
||||
'assistant.history.open': 'Xats recents',
|
||||
'assistant.history.new': 'Xat nou',
|
||||
'assistant.history.empty': 'Encara no hi ha xats.',
|
||||
'assistant.history.retention': 'Els xats s’eliminen al cap de {count} dies.',
|
||||
'assistant.history.retention.one': 'Els xats s’eliminen al cap de {count} dia.',
|
||||
'assistant.history.forever': 'Els xats es conserven fins que els eliminis.',
|
||||
'assistant.error.notConnected': 'L’assistent encara no està connectat.',
|
||||
'assistant.error.busy': 'Ja s’està executant en una altra finestra.',
|
||||
'assistant.error.dailyLimit': 'Per avui ja n’hi ha prou. Fins demà.',
|
||||
'assistant.error.rateLimited': 'Una mica més a poc a poc. Torna-ho a provar d’aquí a un minut.',
|
||||
'assistant.error.needsReauth': 'La teva sessió de {provider} ha caducat.',
|
||||
'assistant.error.usageLimit': 'Has esgotat l’ús de ChatGPT.',
|
||||
'assistant.error.notEligible': 'Aquest compte de ChatGPT no es pot fer servir aquí.',
|
||||
'assistant.error.invalidKey': 'La teva clau de {provider} ja no funciona.',
|
||||
'assistant.error.providerDown': '{provider} no respon ara mateix.',
|
||||
'assistant.error.maxSteps': 'Ha estat molt de cop. Continuo?',
|
||||
'assistant.error.offline': 'Sense connexió. L’assistent necessita internet.',
|
||||
'assistant.error.noBookingsFound': 'No s’ha trobat cap reserva en aquest fitxer.',
|
||||
'assistant.error.importUnavailable': 'La lectura de fitxers de reserva no està disponible aquí.',
|
||||
'assistant.error.generic': 'Alguna cosa ha fallat.',
|
||||
'assistant.errorAction.retry': 'Torna-ho a provar',
|
||||
'assistant.errorAction.continueHere': 'Continua aquí',
|
||||
'assistant.errorAction.continue': 'Continua',
|
||||
'assistant.errorAction.signIn': 'Torna a iniciar sessió',
|
||||
'assistant.errorAction.viewUsage': 'Mostra l’ús',
|
||||
'assistant.errorAction.otherProvider': 'Tria un altre proveïdor',
|
||||
'assistant.errorAction.replaceKey': 'Substitueix la clau',
|
||||
'assistant.errorAction.connectOwn': 'Connecta el teu propi proveïdor',
|
||||
'assistant.admin.policy': 'Qui paga',
|
||||
'assistant.admin.policy.instance': 'Pago jo per a tothom',
|
||||
'assistant.admin.policy.instanceOrUser': 'Pago jo, es permet la pròpia',
|
||||
'assistant.admin.policy.user': 'Cadascú fa servir el seu compte',
|
||||
'assistant.admin.policy.instanceHint': 'Tothom fa servir la connexió de sota i no en pot afegir una de pròpia.',
|
||||
'assistant.admin.policy.instanceOrUserHint':
|
||||
'Tothom fa servir la connexió de sota, tret que en connecti una de pròpia.',
|
||||
'assistant.admin.policy.userHint': 'Cadascú connecta el seu propi proveïdor. No se’t cobra res.',
|
||||
'assistant.admin.connection': 'Connexió per a tothom',
|
||||
'assistant.admin.connectionNone': 'Encara no hi ha cap connexió.',
|
||||
'assistant.admin.connectionRemove': 'Elimina la connexió',
|
||||
'assistant.admin.custom': 'Servidor propi',
|
||||
'assistant.admin.customHint': 'Qualsevol servidor compatible amb OpenAI, per exemple Ollama.',
|
||||
'assistant.admin.baseUrl': 'Adreça del servidor',
|
||||
'assistant.admin.apiKeyOptional': 'Clau API (opcional)',
|
||||
'assistant.admin.prefilled': 'Agafat d’Anàlisi amb IA.',
|
||||
'assistant.admin.dailyLimit': 'Missatges per persona i dia',
|
||||
'assistant.admin.dailyLimitHint': 'Només compta mentre pagues tu.',
|
||||
'assistant.admin.retention': 'Conserva els xats',
|
||||
'assistant.admin.retentionDays': '{count} dies',
|
||||
'assistant.admin.retentionDays.one': '{count} dia',
|
||||
'assistant.admin.retentionForever': 'Per sempre',
|
||||
'assistant.claude.title': 'Connecta amb Claude',
|
||||
'assistant.claude.description':
|
||||
'Fes servir TREK directament a Claude: afegeix-lo com a connector, inicia sessió i ja està.',
|
||||
'assistant.claude.open': 'Obre a Claude',
|
||||
'assistant.claude.scan': 'O escaneja’l amb el mòbil',
|
||||
'assistant.claude.copyUrl': 'Copia l’adreça',
|
||||
'assistant.claude.qrAlt': 'Codi QR per afegir TREK a Claude',
|
||||
'assistant.claude.notPublic': 'Claude només pot accedir a TREK si està disponible per https a internet.',
|
||||
'assistant.mobile.tile': 'Pregunta a TREK',
|
||||
'assistant.mobile.tileHint': 'Planifica amb l’assistent',
|
||||
'assistant.mobile.backToChat': 'Torna al xat',
|
||||
'assistant.step.nearbyAt': 'Cercant a prop de {place}',
|
||||
'assistant.step.searchingFor': 'Cercant {query}',
|
||||
'assistant.step.weatherAt': 'Consultant el temps a {place}',
|
||||
'assistant.change.timeline.show': 'Mostra l’horari',
|
||||
'assistant.change.timeline.hide': 'Amaga l’horari',
|
||||
'assistant.change.timeline.new': 'Nou',
|
||||
'assistant.change.timeline.travel': '≈ {count} min de trajecte',
|
||||
'assistant.change.timeline.travel.one': '≈ {count} min de trajecte',
|
||||
'assistant.change.timeline.gap': '{count} min lliures',
|
||||
'assistant.change.timeline.gap.one': '{count} min lliure',
|
||||
'assistant.change.timeline.overlap': 'Coincideix amb {place}',
|
||||
'assistant.change.timeline.moreDays': '{count} dies més',
|
||||
'assistant.change.timeline.moreDays.one': '{count} dia més',
|
||||
'assistant.change.viewOnMap': 'Mira-ho al mapa',
|
||||
'assistant.change.before': 'Abans',
|
||||
'assistant.change.after': 'Després',
|
||||
'assistant.change.compare': 'Compara al mapa',
|
||||
'assistant.change.undoAll': 'Desfés-ho tot',
|
||||
'assistant.change.undoneAll': '{count} canvis desfets',
|
||||
'assistant.change.undoneAll.one': '{count} canvi desfet',
|
||||
'assistant.change.undoAllPartial': 'Desfets: {done}; modificats mentrestant: {stale}',
|
||||
'assistant.change.undoAllFailed': 'No s’han pogut desfer {count} canvis',
|
||||
'assistant.change.undoAllFailed.one': 'No s’ha pogut desfer {count} canvi',
|
||||
'assistant.place.strip': 'Llocs trobats',
|
||||
'assistant.place.addTo': 'Afegeix a {day}',
|
||||
'assistant.place.addToShort': '+ Dia {n}',
|
||||
'assistant.place.pickDay': 'Tria un dia',
|
||||
'assistant.place.suggested': 'Proposat',
|
||||
'assistant.place.inTrip': 'Ja és al teu viatge',
|
||||
'assistant.place.showOnMap': 'Al mapa',
|
||||
'assistant.place.openNow': 'Obert ara',
|
||||
'assistant.place.closed': 'Tancat',
|
||||
'assistant.place.fromPrev': 'A {distance} de la parada anterior',
|
||||
'assistant.place.more': '{count} més',
|
||||
'assistant.place.more.one': '{count} més',
|
||||
'assistant.mention.days': 'Dies',
|
||||
'assistant.mention.places': 'Llocs',
|
||||
'assistant.mention.bookings': 'Reserves',
|
||||
'assistant.mention.none': 'No s’ha trobat res',
|
||||
'assistant.mention.hint': 'Escriu @ per esmentar dies, llocs i reserves.',
|
||||
'assistant.attach.dropPlace': 'Deixa anar el lloc aquí',
|
||||
'assistant.weather.strip': 'Temps per a {count} dies',
|
||||
'assistant.weather.strip.one': 'Temps per a {count} dia',
|
||||
'assistant.weather.rain': '{pct} % de pluja',
|
||||
'assistant.settings.followMap': 'El mapa segueix l’assistent',
|
||||
'assistant.settings.followMapHint': 'El mapa es desplaça cap on l’assistent cerca i proposa llocs.',
|
||||
'assistant.error.decideFirst': 'Primer decideix sobre les propostes pendents.',
|
||||
'assistant.error.providerLimit': 'S’ha arribat al límit a {provider}. Torna-ho a provar més tard.',
|
||||
'assistant.hint.title': 'Per revisar',
|
||||
'assistant.hint.dismiss': 'Amaga aquest avís',
|
||||
'assistant.hint.more': '{count} més',
|
||||
'assistant.hint.more.one': '{count} més',
|
||||
'assistant.hint.overlap': '{a} i {b} se solapen el {day}',
|
||||
'assistant.hint.overlap.prompt': 'El {day} se solapen {a} i {b}. Com ho resolem?',
|
||||
'assistant.hint.closed': '{place} és tancat el {day}',
|
||||
'assistant.hint.closed.prompt': '{place} és tancat el {day}. Què fem en el seu lloc?',
|
||||
'assistant.hint.opensLate': '{place} no obre fins a les {time} el {day}',
|
||||
'assistant.hint.closesEarly': '{place} tanca ja a les {time} el {day}',
|
||||
'assistant.hint.outsideHours.prompt':
|
||||
'La visita a {place} el {day} no encaixa amb l’horari d’obertura. Com la movem millor?',
|
||||
'assistant.hint.gap': '{day}: {count} hores lliures des de les {from}',
|
||||
'assistant.hint.gap.one': '{day}: {count} hora lliure des de les {from}',
|
||||
'assistant.hint.gap.prompt': 'El {day} no hi ha res previst de {from} a {to}. Què hi encaixaria?',
|
||||
'assistant.hint.longLeg': '{from} i {to} són a {distance} en línia recta',
|
||||
'assistant.hint.longLeg.prompt':
|
||||
'És massa lluny anar de {from} a {to} el {day}? Quina és la millor manera d’arribar-hi?',
|
||||
'assistant.hint.tightLeg': 'Només {count} minuts entre {from} i {to}',
|
||||
'assistant.hint.tightLeg.one': 'Només {count} minut entre {from} i {to}',
|
||||
'assistant.hint.tightLeg.prompt': 'Hi ha prou temps el {day} per anar de {from} a {to}? Què hauríem de canviar?',
|
||||
'assistant.hint.noStay': 'Encara no hi ha allotjament per a {count} nits des del {day}',
|
||||
'assistant.hint.noStay.one': 'Encara no hi ha allotjament per a {count} nit des del {day}',
|
||||
'assistant.hint.noStay.prompt': 'Encara ens falta allotjament des del {day}. Què proposes?',
|
||||
'assistant.hint.unplacedBooking': '{title} no és a cap dia',
|
||||
'assistant.hint.unplacedBookingDay': '{title} segurament va el {day}',
|
||||
'assistant.hint.unplacedBookings': '{count} reserves no són a cap dia',
|
||||
'assistant.hint.unplacedBookings.one': '{count} reserva no és a cap dia',
|
||||
'assistant.hint.unplacedBooking.prompt': 'Quines reserves no són a cap dia, i on van?',
|
||||
'assistant.hint.menuOn': 'Mostra els avisos',
|
||||
'assistant.hint.menuOff': 'No mostris avisos',
|
||||
'assistant.launcher.withHints': 'Assistent, {count} avisos',
|
||||
'assistant.launcher.withHints.one': 'Assistent, {count} avís',
|
||||
'assistant.mobile.tileHints': '{count} coses per revisar al teu pla',
|
||||
'assistant.mobile.tileHints.one': '{count} cosa per revisar al teu pla',
|
||||
'assistant.share.button': 'Comparteix al xat del viatge',
|
||||
'assistant.share.title': 'Comparteix al xat del viatge',
|
||||
'assistant.share.who': 'Tothom al viatge veurà aquest missatge, marcat com de l’assistent.',
|
||||
'assistant.share.includeChanges': 'Afegeix els canvis aplicats',
|
||||
'assistant.share.changesHeading': 'Aplicat:',
|
||||
'assistant.share.truncated': 'Escurçat a {count} caràcters',
|
||||
'assistant.share.truncated.one': 'Escurçat a {count} caràcter',
|
||||
'assistant.share.send': 'Comparteix',
|
||||
'assistant.share.done': 'Compartit al xat del viatge',
|
||||
'assistant.share.shared': 'Compartit',
|
||||
'assistant.share.failed': 'No s’ha pogut compartir. Torna-ho a provar d’aquí a un moment.',
|
||||
'assistant.share.chatOff': 'El xat del viatge està desactivat.',
|
||||
};
|
||||
export default assistant;
|
||||
@@ -106,5 +106,7 @@ const collab: TranslationStrings = {
|
||||
'collab.chat.attachImages': 'Adjunta imatges',
|
||||
'collab.chat.attachedImage': 'Imatge adjunta',
|
||||
'collab.chat.closeImage': 'Tanca la imatge',
|
||||
'collab.chat.fromAssistant': 'de l’assistent',
|
||||
'collab.chat.fromAssistantHint': '{name} ha compartit aquesta resposta de l’assistent',
|
||||
};
|
||||
export default collab;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import admin from './admin';
|
||||
import airport from './airport';
|
||||
import assistant from './assistant';
|
||||
import atlas from './atlas';
|
||||
import backup from './backup';
|
||||
import budget from './budget';
|
||||
@@ -116,5 +117,6 @@ const locale = {
|
||||
...dawarich,
|
||||
...docsync,
|
||||
...ferratas,
|
||||
...assistant,
|
||||
};
|
||||
export default locale;
|
||||
|
||||
@@ -84,6 +84,7 @@ const oauth: TranslationStrings = {
|
||||
'oauth.authorize.loginButton': 'Inicia la sessió a TREK',
|
||||
'oauth.authorize.requestLabel': "Sol·licitud d'autorització",
|
||||
'oauth.authorize.requestDescription': 'Aquesta aplicació està sol·licitant accés al teu compte de TREK.',
|
||||
'oauth.authorize.verifiedHost': 'Domini verificat: {host}',
|
||||
'oauth.authorize.trustNote':
|
||||
'Només atorga accés a aplicacions de confiança. Les teves dades es mantenen al teu servidor.',
|
||||
'oauth.authorize.selectScope': 'Selecciona almenys un àmbit',
|
||||
|
||||
@@ -632,6 +632,9 @@ const admin: TranslationStrings = {
|
||||
'admin.addons.catalog.llm_parsing.name': 'Analýza pomocí AI',
|
||||
'admin.addons.catalog.llm_parsing.description':
|
||||
'Přečte rezervace, které vestavěný analyzátor nezvládne, pomocí zvoleného modelu AI',
|
||||
'admin.addons.catalog.ai_assistant.name': 'AI asistent',
|
||||
'admin.addons.catalog.ai_assistant.description':
|
||||
'Chat v každé cestě, který odpovídá na otázky a navrhuje změny, které potvrdíš',
|
||||
'admin.addons.llm.vision.auto': 'Automaticky',
|
||||
'admin.addons.llm.vision.on': 'Ano',
|
||||
'admin.addons.llm.vision.off': 'Ne',
|
||||
|
||||
@@ -0,0 +1,361 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistant: TranslationStrings = {
|
||||
'assistant.launcher.tooltip': 'Asistent',
|
||||
'assistant.launcher.ctrlKey': 'Ctrl', // same-as-en
|
||||
'assistant.picker.title': 'Která cesta?',
|
||||
'assistant.picker.empty': 'Nejdřív vytvoř cestu, pak ti ji asistent pomůže naplánovat.',
|
||||
'assistant.picker.now': 'Probíhá',
|
||||
'assistant.picker.next': 'Další',
|
||||
'assistant.window.title': 'Asistent',
|
||||
'assistant.window.log': 'Konverzace s asistentem',
|
||||
'assistant.window.menu': 'Další možnosti',
|
||||
'assistant.window.settings': 'Nastavení AI',
|
||||
'assistant.window.deleteThread': 'Smazat chat',
|
||||
'assistant.window.deleteConfirm': 'Smazat tento chat? Nelze to vrátit zpět.',
|
||||
'assistant.setup.question': 'S čím chceš chatovat?',
|
||||
'assistant.setup.beta': 'Beta', // same-as-en
|
||||
'assistant.setup.chatgptNote': 'Klíč API je oficiální cesta. Přihlášení přes ChatGPT je zatím ve fázi beta.',
|
||||
'assistant.setup.keySubtitle': 'Anthropic, OpenAI, Gemini nebo OpenRouter',
|
||||
'assistant.setup.adminOnly': 'Tohle nastavuje tvůj administrátor.',
|
||||
'assistant.setup.forEveryone': 'Nastavit pro všechny',
|
||||
'assistant.connect.title': 'AI asistent',
|
||||
'assistant.connect.description': 'Chatuj s asistentem, který zná tvou cestu a navrhuje změny, které potvrdíš.',
|
||||
'assistant.connect.chatgpt': 'Přihlásit se přes ChatGPT',
|
||||
'assistant.connect.chatgptHint': 'Využije tvůj tarif ChatGPT. Klíč není potřeba.',
|
||||
'assistant.connect.chatgptStep1': 'Přihlas se k ChatGPT v nové kartě.',
|
||||
'assistant.connect.chatgptOpen': 'Otevřít přihlášení k ChatGPT',
|
||||
'assistant.connect.chatgptStep2':
|
||||
'Prohlížeč pak zobrazí chybovou stránku. To je v pořádku. Zkopíruj adresu nahoře a vrať se sem.',
|
||||
'assistant.connect.chatgptPaste': 'Vložit adresu',
|
||||
'assistant.connect.chatgptWrong': 'To není správná adresa. Začíná na 127.0.0.1.',
|
||||
'assistant.connect.chatgptExpired': 'Trvalo to příliš dlouho. Přihlas se prosím znovu.',
|
||||
'assistant.connect.chatgptOtherAccount': 'Tohle je jiný účet ChatGPT. Nejdřív odeber připojení.',
|
||||
'assistant.connect.key': 'Vložit klíč API',
|
||||
'assistant.connect.keyPlaceholder': 'Sem vlož svůj klíč',
|
||||
'assistant.connect.detected': 'Rozpoznáno: {provider}',
|
||||
'assistant.connect.whichProvider': 'Od kterého poskytovatele je tento klíč?',
|
||||
'assistant.connect.testing': 'Kontroluji připojení',
|
||||
'assistant.connect.ok': 'Připojeno. Jdeme na to.',
|
||||
'assistant.connect.openAssistant': 'Otevřít asistenta',
|
||||
'assistant.connect.badKey': 'Tento klíč nefunguje.',
|
||||
'assistant.connect.unknownModel': 'Tento model tu není k dispozici.',
|
||||
'assistant.connect.getKey': 'Získat klíč',
|
||||
'assistant.connect.hintAnthropic': 'Claude Max a Team zahrnují kredity API.',
|
||||
'assistant.connect.hintOpenai': 'Vytvoř klíč na svém panelu OpenAI.',
|
||||
'assistant.connect.hintGemini': 'AI Studio má bezplatnou úroveň s limity za minutu a za den, ve které může Google to, co odešleš, použít ke zlepšení svých produktů. Se zapnutou fakturací jsou limity vyšší a to neplatí.',
|
||||
'assistant.connect.hintOpenrouter': 'Přihlas se přes OpenRouter, žádný klíč kopírovat nemusíš.',
|
||||
'assistant.connect.openrouter': 'Připojit OpenRouter',
|
||||
'assistant.connect.save': 'Připojit',
|
||||
'assistant.connect.model': 'Model', // same-as-en
|
||||
'assistant.connect.modelHint': 'Zobrazují se jen modely, které umí používat nástroje.',
|
||||
'assistant.connect.free': 'K dispozici je bezplatná úroveň',
|
||||
'assistant.connect.paid': 'Platba podle využití',
|
||||
'assistant.model.recommended': 'Doporučeno',
|
||||
'assistant.model.free': 'Zdarma',
|
||||
'assistant.connect.connected': 'Připojeno k {provider}',
|
||||
'assistant.connect.providedByAdmin': 'Poskytuje tvůj administrátor',
|
||||
'assistant.connect.disconnect': 'Odpojit',
|
||||
'assistant.connect.disconnectConfirm': 'Odpojit {provider}? Tvoje chaty zůstanou.',
|
||||
'assistant.connect.signOut': 'Odhlásit se',
|
||||
'assistant.connect.statusOk': 'Připojeno',
|
||||
'assistant.connect.statusNeedsReauth': 'Přihlášení vypršelo',
|
||||
'assistant.connect.statusSignedOut': 'Odhlášeno',
|
||||
'assistant.connect.statusError': 'Nedostupné',
|
||||
'assistant.connect.quota': 'Dnes využito {used} z {count} zpráv',
|
||||
'assistant.connect.quota.one': 'Dnes využito {used} z {count} zprávy',
|
||||
'assistant.connect.quota.few': 'Dnes využito {used} z {count} zpráv',
|
||||
'assistant.connect.claudeLink': 'Používáš Claude? Připoj se v Claude',
|
||||
'assistant.privacy.what': 'Co se sdílí?',
|
||||
'assistant.privacy.banner': 'Tvoje zprávy a data cesty, která asistent čte, jdou do {provider}.',
|
||||
'assistant.privacy.ok': 'Rozumím',
|
||||
'assistant.privacy.shared':
|
||||
'Tvoje zprávy a podrobnosti o cestě, které asistent čte, například místa, dny a rezervace.',
|
||||
'assistant.privacy.notShared': 'Ne tvoje heslo, tvoje soubory ani tvoje další cesty.',
|
||||
'assistant.privacy.provider': '{provider} s nimi nakládá podle vlastních zásad ochrany soukromí.',
|
||||
'assistant.empty.title': 'Co budeme plánovat?',
|
||||
'assistant.empty.readOnly': 'Tuhle cestu můžeš jen prohlížet, takže odpovím na otázky, ale nic nezměním.',
|
||||
'assistant.suggest.planFirstDays': 'Naplánuj mi první dny',
|
||||
'assistant.suggest.mustSee': 'Co si tu nesmím nechat ujít?',
|
||||
'assistant.suggest.packingList': 'Udělej seznam věcí na tuto cestu',
|
||||
'assistant.suggest.emptyDays': 'Které dny jsou ještě prázdné?',
|
||||
'assistant.suggest.rainPlan': 'Co můžeme dělat, když bude pršet?',
|
||||
'assistant.suggest.today': 'Co je dnes na programu?',
|
||||
'assistant.suggest.eatNearby': 'Najdi poblíž něco k jídlu',
|
||||
'assistant.suggest.weather': 'Jaké bude počasí?',
|
||||
'assistant.suggest.openTodos': 'Co ještě zbývá na seznamu úkolů?',
|
||||
'assistant.suggest.settleUp': 'Kdo komu dluží?',
|
||||
'assistant.suggest.recap': 'Shrň naši cestu',
|
||||
'assistant.suggest.nearPlace': 'Co dalšího je poblíž {place}?',
|
||||
'assistant.suggest.placeTips': 'Tipy k návštěvě {place}',
|
||||
'assistant.suggest.planDay': 'Naplánuj mi {day}',
|
||||
'assistant.suggest.dayTooFull': 'Není {day} moc nabitý?',
|
||||
'assistant.thread.scrollDown': 'Přejít na poslední zprávu',
|
||||
'assistant.attach.button': 'Přiložit rezervaci',
|
||||
'assistant.attach.drop': 'Pusť to sem a přečtu rezervaci',
|
||||
'assistant.attach.unsupported': 'Tento typ souboru neumím přečíst. Zkus PDF, e-mail, obrázek nebo kartu do peněženky.',
|
||||
'assistant.attach.tooLarge': 'Tento soubor je větší než 10 MB.',
|
||||
'assistant.composer.placeholder': 'Zeptej se na cokoli o {trip}',
|
||||
'assistant.composer.send': 'Odeslat',
|
||||
'assistant.composer.stop': 'Zastavit',
|
||||
'assistant.composer.decideFirst': 'Nejdřív použij nebo zahoď změny výše.',
|
||||
'assistant.context.ask': 'Zeptat se',
|
||||
'assistant.context.remove': 'Odebrat kontext',
|
||||
'assistant.step.reading': 'Čtu tvůj plán',
|
||||
'assistant.step.searching': 'Hledám místa',
|
||||
'assistant.step.details': 'Zjišťuji podrobnosti',
|
||||
'assistant.step.nearby': 'Hledám v okolí',
|
||||
'assistant.step.categories': 'Kontroluji kategorie',
|
||||
'assistant.step.weather': 'Zjišťuji počasí',
|
||||
'assistant.step.todos': 'Čtu seznam úkolů',
|
||||
'assistant.step.costs': 'Kontroluji náklady',
|
||||
'assistant.step.help': 'Hledám v nápovědě',
|
||||
'assistant.step.readingBooking': 'Čtu rezervaci',
|
||||
'assistant.step.working': 'Pracuji na tom',
|
||||
'assistant.step.collapsed': '{count} kroků',
|
||||
'assistant.step.collapsed.one': '{count} krok',
|
||||
'assistant.step.collapsed.few': '{count} kroky',
|
||||
'assistant.change.title': 'Navrhované změny',
|
||||
'assistant.change.doneTitle': 'Hotovo',
|
||||
'assistant.change.apply': 'Použít',
|
||||
'assistant.change.applyN': 'Použít {count}',
|
||||
'assistant.change.applyN.one': 'Použít {count}',
|
||||
'assistant.change.applyN.few': 'Použít {count}',
|
||||
'assistant.change.discard': 'Zahodit',
|
||||
'assistant.change.include': 'Zahrnout tuto změnu',
|
||||
'assistant.change.applied': 'Použito',
|
||||
'assistant.change.discarded': 'Zahozeno',
|
||||
'assistant.change.failed': 'Tohle se nepodařilo použít.',
|
||||
'assistant.change.applying': 'Používám',
|
||||
'assistant.change.expired': 'Už není k dispozici',
|
||||
'assistant.change.undo': 'Vrátit zpět',
|
||||
'assistant.change.undone': 'Vráceno',
|
||||
'assistant.change.undoStale': 'Mezitím se to změnilo. Uprav to prosím ručně.',
|
||||
'assistant.change.undoFailed': 'Tohle se nepodařilo vrátit.',
|
||||
'assistant.change.showOnMap': 'Zobrazit na mapě',
|
||||
'assistant.change.empty': 'prázdné',
|
||||
'assistant.change.pending': 'Čeká na tebe {count} změn',
|
||||
'assistant.change.pending.one': 'Čeká na tebe {count} změna',
|
||||
'assistant.change.pending.few': 'Čekají na tebe {count} změny',
|
||||
'assistant.change.verb.addPlace': 'Přidat místo',
|
||||
'assistant.change.verb.addPlaceToDay': 'Přidat místo ke dni',
|
||||
'assistant.change.verb.updatePlace': 'Změnit místo',
|
||||
'assistant.change.verb.assignPlace': 'Naplánovat místo na den',
|
||||
'assistant.change.verb.moveAssignment': 'Přesunout na jiný den',
|
||||
'assistant.change.verb.removeFromDay': 'Odebrat ze dne',
|
||||
'assistant.change.verb.addDayNote': 'Přidat poznámku',
|
||||
'assistant.change.verb.updateDayNote': 'Změnit poznámku',
|
||||
'assistant.change.verb.addPackingItem': 'Přidat do seznamu věcí',
|
||||
'assistant.change.verb.updatePackingItem': 'Změnit položku k zabalení',
|
||||
'assistant.change.verb.addTodo': 'Přidat úkol',
|
||||
'assistant.change.verb.addBudgetItem': 'Přidat výdaj',
|
||||
'assistant.change.verb.addReservation': 'Přidat rezervaci',
|
||||
'assistant.change.verb.importBooking': 'Přidat rezervaci ze souboru',
|
||||
'assistant.change.label.name': 'Název',
|
||||
'assistant.change.label.description': 'Popis',
|
||||
'assistant.change.label.address': 'Adresa',
|
||||
'assistant.change.label.coordinates': 'Poloha na mapě',
|
||||
'assistant.change.label.category': 'Kategorie',
|
||||
'assistant.change.label.day': 'Den',
|
||||
'assistant.change.label.time': 'Čas',
|
||||
'assistant.change.label.endTime': 'Do',
|
||||
'assistant.change.label.notes': 'Poznámky',
|
||||
'assistant.change.label.website': 'Web',
|
||||
'assistant.change.label.phone': 'Telefon',
|
||||
'assistant.change.label.email': 'E-mail',
|
||||
'assistant.change.label.openingHours': 'Otevírací doba',
|
||||
'assistant.change.label.price': 'Cena',
|
||||
'assistant.change.label.currency': 'Měna',
|
||||
'assistant.change.label.stopType': 'Typ zastávky',
|
||||
'assistant.change.label.text': 'Text', // same-as-en
|
||||
'assistant.change.label.icon': 'Ikona',
|
||||
'assistant.change.label.color': 'Barva',
|
||||
'assistant.change.label.position': 'Pořadí',
|
||||
'assistant.change.label.quantity': 'Množství',
|
||||
'assistant.change.label.weight': 'Hmotnost',
|
||||
'assistant.change.label.bag': 'Zavazadlo',
|
||||
'assistant.change.label.packed': 'Zabaleno',
|
||||
'assistant.change.label.dueDate': 'Termín',
|
||||
'assistant.change.label.priority': 'Priorita',
|
||||
'assistant.change.label.assignee': 'Přiřazeno',
|
||||
'assistant.change.label.type': 'Typ',
|
||||
'assistant.change.label.location': 'Místo',
|
||||
'assistant.change.label.link': 'Odkaz',
|
||||
'assistant.change.label.confirmation': 'Číslo potvrzení',
|
||||
'assistant.change.label.checkIn': 'Příjezd',
|
||||
'assistant.change.label.checkOut': 'Odjezd',
|
||||
'assistant.change.label.stay': 'Pobyt',
|
||||
'assistant.change.label.members': 'Rozděleno mezi',
|
||||
'assistant.change.label.route': 'Trasa',
|
||||
'assistant.history.open': 'Poslední chaty',
|
||||
'assistant.history.new': 'Nový chat',
|
||||
'assistant.history.empty': 'Zatím žádné chaty.',
|
||||
'assistant.history.retention': 'Chaty se mažou po {count} dnech.',
|
||||
'assistant.history.retention.one': 'Chaty se mažou po {count} dni.',
|
||||
'assistant.history.retention.few': 'Chaty se mažou po {count} dnech.',
|
||||
'assistant.history.forever': 'Chaty zůstanou, dokud je nesmažeš.',
|
||||
'assistant.error.notConnected': 'Asistent ještě není připojený.',
|
||||
'assistant.error.busy': 'Už běží v jiném okně.',
|
||||
'assistant.error.dailyLimit': 'Pro dnešek je to všechno. Uvidíme se zítra.',
|
||||
'assistant.error.rateLimited': 'Trochu zpomal. Zkus to znovu za minutu.',
|
||||
'assistant.error.needsReauth': 'Tvoje přihlášení k {provider} vypršelo.',
|
||||
'assistant.error.usageLimit': 'Tvůj limit využití ChatGPT je vyčerpaný.',
|
||||
'assistant.error.notEligible': 'Tento účet ChatGPT tu nelze použít.',
|
||||
'assistant.error.invalidKey': 'Tvůj klíč pro {provider} už nefunguje.',
|
||||
'assistant.error.providerDown': '{provider} teď neodpovídá.',
|
||||
'assistant.error.maxSteps': 'Bylo toho najednou hodně. Mám pokračovat?',
|
||||
'assistant.error.offline': 'Offline. Asistent potřebuje internet.',
|
||||
'assistant.error.noBookingsFound': 'V tomto souboru nebyla nalezena žádná rezervace.',
|
||||
'assistant.error.importUnavailable': 'Čtení souborů s rezervacemi tu není k dispozici.',
|
||||
'assistant.error.generic': 'Něco se pokazilo.',
|
||||
'assistant.errorAction.retry': 'Zkusit znovu',
|
||||
'assistant.errorAction.continueHere': 'Pokračovat tady',
|
||||
'assistant.errorAction.continue': 'Pokračovat',
|
||||
'assistant.errorAction.signIn': 'Přihlásit se znovu',
|
||||
'assistant.errorAction.viewUsage': 'Zobrazit využití',
|
||||
'assistant.errorAction.otherProvider': 'Vybrat jiného poskytovatele',
|
||||
'assistant.errorAction.replaceKey': 'Nahradit klíč',
|
||||
'assistant.errorAction.connectOwn': 'Připojit vlastního poskytovatele',
|
||||
'assistant.admin.policy': 'Kdo platí',
|
||||
'assistant.admin.policy.instance': 'Platím za všechny',
|
||||
'assistant.admin.policy.instanceOrUser': 'Platím já, vlastní povolen',
|
||||
'assistant.admin.policy.user': 'Každý používá vlastní účet',
|
||||
'assistant.admin.policy.instanceHint': 'Všichni používají připojení níže a nemohou přidat vlastní.',
|
||||
'assistant.admin.policy.instanceOrUserHint': 'Všichni používají připojení níže, pokud nepřipojí vlastní.',
|
||||
'assistant.admin.policy.userHint': 'Každý si připojí vlastního poskytovatele. Tobě se nic neúčtuje.',
|
||||
'assistant.admin.connection': 'Připojení pro všechny',
|
||||
'assistant.admin.connectionNone': 'Zatím žádné připojení.',
|
||||
'assistant.admin.connectionRemove': 'Odebrat připojení',
|
||||
'assistant.admin.custom': 'Vlastní server',
|
||||
'assistant.admin.customHint': 'Jakýkoli server kompatibilní s OpenAI, například Ollama.',
|
||||
'assistant.admin.baseUrl': 'Adresa serveru',
|
||||
'assistant.admin.apiKeyOptional': 'Klíč API (nepovinné)',
|
||||
'assistant.admin.prefilled': 'Převzato z Analýzy pomocí AI.',
|
||||
'assistant.admin.dailyLimit': 'Zpráv na osobu za den',
|
||||
'assistant.admin.dailyLimitHint': 'Počítá se jen, dokud platíš.',
|
||||
'assistant.admin.retention': 'Uchovávat chaty',
|
||||
'assistant.admin.retentionDays': '{count} dní',
|
||||
'assistant.admin.retentionDays.one': '{count} den',
|
||||
'assistant.admin.retentionDays.few': '{count} dny',
|
||||
'assistant.admin.retentionForever': 'Navždy',
|
||||
'assistant.claude.title': 'Připojit přes Claude',
|
||||
'assistant.claude.description': 'Používej TREK přímo v Claude: přidej ho jako konektor, přihlas se a hotovo.',
|
||||
'assistant.claude.open': 'Otevřít v Claude',
|
||||
'assistant.claude.scan': 'Nebo ho naskenuj telefonem',
|
||||
'assistant.claude.copyUrl': 'Kopírovat adresu',
|
||||
'assistant.claude.qrAlt': 'QR kód pro přidání TREK do Claude',
|
||||
'assistant.claude.notPublic': 'Claude se k TREK dostane, jen když je dostupný přes https na internetu.',
|
||||
'assistant.mobile.tile': 'Zeptej se TREK',
|
||||
'assistant.mobile.tileHint': 'Plánuj s asistentem',
|
||||
'assistant.mobile.backToChat': 'Zpět do chatu',
|
||||
'assistant.step.nearbyAt': 'Hledá poblíž místa {place}',
|
||||
'assistant.step.searchingFor': 'Hledá {query}',
|
||||
'assistant.step.weatherAt': 'Zjišťuje počasí v místě {place}',
|
||||
'assistant.change.timeline.show': 'Zobrazit program',
|
||||
'assistant.change.timeline.hide': 'Skrýt program',
|
||||
'assistant.change.timeline.new': 'Nové',
|
||||
'assistant.change.timeline.travel': '≈ {count} min cesty',
|
||||
'assistant.change.timeline.travel.one': '≈ {count} min cesty',
|
||||
'assistant.change.timeline.travel.few': '≈ {count} min cesty',
|
||||
'assistant.change.timeline.gap': '{count} min volno',
|
||||
'assistant.change.timeline.gap.one': '{count} min volno',
|
||||
'assistant.change.timeline.gap.few': '{count} min volno',
|
||||
'assistant.change.timeline.overlap': 'Překrývá se s {place}',
|
||||
'assistant.change.timeline.moreDays': 'Dalších {count} dní',
|
||||
'assistant.change.timeline.moreDays.one': 'Další {count} den',
|
||||
'assistant.change.timeline.moreDays.few': 'Další {count} dny',
|
||||
'assistant.change.viewOnMap': 'Zobrazit na mapě',
|
||||
'assistant.change.before': 'Předtím',
|
||||
'assistant.change.after': 'Potom',
|
||||
'assistant.change.compare': 'Porovnat na mapě',
|
||||
'assistant.change.undoAll': 'Vrátit vše',
|
||||
'assistant.change.undoneAll': 'Vráceno {count} změn',
|
||||
'assistant.change.undoneAll.one': 'Vrácena {count} změna',
|
||||
'assistant.change.undoneAll.few': 'Vráceny {count} změny',
|
||||
'assistant.change.undoAllPartial': 'Vráceno: {done}, mezitím změněno: {stale}',
|
||||
'assistant.change.undoAllFailed': '{count} změn nešlo vrátit',
|
||||
'assistant.change.undoAllFailed.one': '{count} změnu nešlo vrátit',
|
||||
'assistant.change.undoAllFailed.few': '{count} změny nešlo vrátit',
|
||||
'assistant.place.strip': 'Nalezená místa',
|
||||
'assistant.place.addTo': 'Přidat do {day}',
|
||||
'assistant.place.addToShort': '+ Den {n}',
|
||||
'assistant.place.pickDay': 'Vybrat den',
|
||||
'assistant.place.suggested': 'Navrženo',
|
||||
'assistant.place.inTrip': 'Už je ve tvém výletu',
|
||||
'assistant.place.showOnMap': 'Na mapě',
|
||||
'assistant.place.openNow': 'Teď otevřeno',
|
||||
'assistant.place.closed': 'Zavřeno',
|
||||
'assistant.place.fromPrev': '{distance} od předchozí zastávky',
|
||||
'assistant.place.more': 'Dalších {count}',
|
||||
'assistant.place.more.one': 'Další {count}',
|
||||
'assistant.place.more.few': 'Další {count}',
|
||||
'assistant.mention.days': 'Dny',
|
||||
'assistant.mention.places': 'Místa',
|
||||
'assistant.mention.bookings': 'Rezervace',
|
||||
'assistant.mention.none': 'Nic nenalezeno',
|
||||
'assistant.mention.hint': 'Napiš @ a zmiň dny, místa nebo rezervace.',
|
||||
'assistant.attach.dropPlace': 'Pusť místo sem',
|
||||
'assistant.weather.strip': 'Počasí na {count} dní',
|
||||
'assistant.weather.strip.one': 'Počasí na {count} den',
|
||||
'assistant.weather.strip.few': 'Počasí na {count} dny',
|
||||
'assistant.weather.rain': '{pct} % déšť',
|
||||
'assistant.settings.followMap': 'Mapa sleduje asistenta',
|
||||
'assistant.settings.followMapHint': 'Mapa se posune tam, kde asistent hledá a navrhuje místa.',
|
||||
'assistant.error.decideFirst': 'Nejdřív rozhodni o otevřených návrzích.',
|
||||
'assistant.error.providerLimit': 'Limit u {provider} je vyčerpán. Zkus to později.',
|
||||
'assistant.hint.title': 'Stojí za pohled',
|
||||
'assistant.hint.dismiss': 'Skrýt tento tip',
|
||||
'assistant.hint.more': 'Dalších {count}',
|
||||
'assistant.hint.more.one': 'Další {count}',
|
||||
'assistant.hint.more.few': 'Další {count}',
|
||||
'assistant.hint.overlap': '{a} a {b} se překrývají: {day}',
|
||||
'assistant.hint.overlap.prompt': '{day} se překrývají {a} a {b}. Jak to vyřešíme?',
|
||||
'assistant.hint.closed': '{place} má zavřeno: {day}',
|
||||
'assistant.hint.closed.prompt': '{place} má zavřeno: {day}. Co uděláme místo toho?',
|
||||
'assistant.hint.opensLate': '{place} otevírá až v {time} ({day})',
|
||||
'assistant.hint.closesEarly': '{place} zavírá už v {time} ({day})',
|
||||
'assistant.hint.outsideHours.prompt':
|
||||
'Naše návštěva {place} ({day}) nesedí s otevírací dobou. Jak ji nejlépe posuneme?',
|
||||
'assistant.hint.gap': '{day}: {count} hodin volno od {from}',
|
||||
'assistant.hint.gap.one': '{day}: {count} hodina volno od {from}',
|
||||
'assistant.hint.gap.few': '{day}: {count} hodiny volno od {from}',
|
||||
'assistant.hint.gap.prompt': '{day} není od {from} do {to} nic naplánováno. Co by se tam hodilo?',
|
||||
'assistant.hint.longLeg': '{from} a {to} dělí vzdušnou čarou {distance}',
|
||||
'assistant.hint.longLeg.prompt': 'Není to {day} z {from} do {to} moc daleko? Jak se tam nejlépe dostaneme?',
|
||||
'assistant.hint.tightLeg': 'Mezi {from} a {to} je jen {count} minut',
|
||||
'assistant.hint.tightLeg.one': 'Mezi {from} a {to} je jen {count} minuta',
|
||||
'assistant.hint.tightLeg.few': 'Mezi {from} a {to} jsou jen {count} minuty',
|
||||
'assistant.hint.tightLeg.prompt': 'Stihneme {day} cestu z {from} do {to}? Co bychom měli změnit?',
|
||||
'assistant.hint.noStay': 'Zatím žádné ubytování na {count} nocí od {day}',
|
||||
'assistant.hint.noStay.one': 'Zatím žádné ubytování na {count} noc od {day}',
|
||||
'assistant.hint.noStay.few': 'Zatím žádné ubytování na {count} noci od {day}',
|
||||
'assistant.hint.noStay.prompt': 'Od {day} nám ještě chybí ubytování. Co navrhuješ?',
|
||||
'assistant.hint.unplacedBooking': '{title} není u žádného dne',
|
||||
'assistant.hint.unplacedBookingDay': '{title} nejspíš patří k {day}',
|
||||
'assistant.hint.unplacedBookings': '{count} rezervací není u žádného dne',
|
||||
'assistant.hint.unplacedBookings.one': '{count} rezervace není u žádného dne',
|
||||
'assistant.hint.unplacedBookings.few': '{count} rezervace nejsou u žádného dne',
|
||||
'assistant.hint.unplacedBooking.prompt': 'Které rezervace nejsou u žádného dne a kam patří?',
|
||||
'assistant.hint.menuOn': 'Zobrazovat tipy',
|
||||
'assistant.hint.menuOff': 'Nezobrazovat tipy',
|
||||
'assistant.launcher.withHints': 'Asistent, {count} tipů',
|
||||
'assistant.launcher.withHints.one': 'Asistent, {count} tip',
|
||||
'assistant.launcher.withHints.few': 'Asistent, {count} tipy',
|
||||
'assistant.mobile.tileHints': '{count} věcí ke kontrole v plánu',
|
||||
'assistant.mobile.tileHints.one': '{count} věc ke kontrole v plánu',
|
||||
'assistant.mobile.tileHints.few': '{count} věci ke kontrole v plánu',
|
||||
'assistant.share.button': 'Sdílet v chatu výletu',
|
||||
'assistant.share.title': 'Sdílet v chatu výletu',
|
||||
'assistant.share.who': 'Všichni ve výletu uvidí tuto zprávu, označenou jako od asistenta.',
|
||||
'assistant.share.includeChanges': 'Přidat použité změny',
|
||||
'assistant.share.changesHeading': 'Použito:',
|
||||
'assistant.share.truncated': 'Zkráceno na {count} znaků',
|
||||
'assistant.share.truncated.one': 'Zkráceno na {count} znak',
|
||||
'assistant.share.truncated.few': 'Zkráceno na {count} znaky',
|
||||
'assistant.share.send': 'Sdílet',
|
||||
'assistant.share.done': 'Sdíleno v chatu výletu',
|
||||
'assistant.share.shared': 'Sdíleno',
|
||||
'assistant.share.failed': 'Sdílení se nepovedlo. Zkus to za chvíli znovu.',
|
||||
'assistant.share.chatOff': 'Chat výletu je vypnutý.',
|
||||
};
|
||||
export default assistant;
|
||||
@@ -111,5 +111,7 @@ const collab: TranslationStrings = {
|
||||
'collab.chat.attachImages': 'Připojit obrázky',
|
||||
'collab.chat.attachedImage': 'Připojený obrázek',
|
||||
'collab.chat.closeImage': 'Zavřít obrázek',
|
||||
'collab.chat.fromAssistant': 'od asistenta',
|
||||
'collab.chat.fromAssistantHint': '{name} sdílel(a) tuto odpověď asistenta',
|
||||
};
|
||||
export default collab;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import admin from './admin';
|
||||
import airport from './airport';
|
||||
import assistant from './assistant';
|
||||
import atlas from './atlas';
|
||||
import backup from './backup';
|
||||
import budget from './budget';
|
||||
@@ -116,5 +117,6 @@ const locale = {
|
||||
...dawarich,
|
||||
...docsync,
|
||||
...ferratas,
|
||||
...assistant,
|
||||
};
|
||||
export default locale;
|
||||
|
||||
@@ -82,6 +82,7 @@ const oauth: TranslationStrings = {
|
||||
'oauth.authorize.loginButton': 'Sign in to TREK', // en-fallback
|
||||
'oauth.authorize.requestLabel': 'Authorization Request', // en-fallback
|
||||
'oauth.authorize.requestDescription': 'This application is requesting access to your TREK account.', // en-fallback
|
||||
'oauth.authorize.verifiedHost': 'Ověřená doména: {host}',
|
||||
'oauth.authorize.trustNote': 'Only grant access to applications you trust. Your data stays on your server.', // en-fallback
|
||||
'oauth.authorize.selectScope': 'Select at least one scope', // en-fallback
|
||||
'oauth.authorize.approveScopes': 'Schválit ({count} oprávnění)',
|
||||
|
||||
@@ -642,6 +642,9 @@ const admin: TranslationStrings = {
|
||||
'admin.addons.catalog.llm_parsing.name': 'KI-Auswertung',
|
||||
'admin.addons.catalog.llm_parsing.description':
|
||||
'Liest Buchungen, an denen der eingebaute Parser scheitert, mit einem KI-Modell deiner Wahl',
|
||||
'admin.addons.catalog.ai_assistant.name': 'KI-Assistent',
|
||||
'admin.addons.catalog.ai_assistant.description':
|
||||
'Ein Chat in jeder Reise, der Fragen beantwortet und Änderungen vorschlägt, die du bestätigst',
|
||||
'admin.addons.llm.vision.auto': 'Automatisch',
|
||||
'admin.addons.llm.vision.on': 'Ja',
|
||||
'admin.addons.llm.vision.off': 'Nein',
|
||||
|
||||
@@ -0,0 +1,342 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistant: TranslationStrings = {
|
||||
'assistant.launcher.tooltip': 'Assistent',
|
||||
'assistant.launcher.ctrlKey': 'Strg',
|
||||
'assistant.picker.title': 'Welche Reise?',
|
||||
'assistant.picker.empty': 'Leg zuerst eine Reise an, dann hilft dir der Assistent beim Planen.',
|
||||
'assistant.picker.now': 'Jetzt',
|
||||
'assistant.picker.next': 'Als Nächstes',
|
||||
'assistant.window.title': 'Assistent',
|
||||
'assistant.window.log': 'Unterhaltung mit dem Assistenten',
|
||||
'assistant.window.menu': 'Weitere Optionen',
|
||||
'assistant.window.settings': 'KI-Einstellungen',
|
||||
'assistant.window.deleteThread': 'Unterhaltung löschen',
|
||||
'assistant.window.deleteConfirm': 'Diese Unterhaltung löschen? Das lässt sich nicht rückgängig machen.',
|
||||
'assistant.setup.question': 'Womit möchtest du chatten?',
|
||||
'assistant.setup.beta': 'Beta', // same-as-en
|
||||
'assistant.setup.chatgptNote':
|
||||
'Der offizielle Weg ist ein API-Schlüssel. Die Anmeldung mit ChatGPT ist noch eine Beta.',
|
||||
'assistant.setup.keySubtitle': 'Anthropic, OpenAI, Gemini oder OpenRouter',
|
||||
'assistant.setup.adminOnly': 'Dein Admin richtet das ein.',
|
||||
'assistant.setup.forEveryone': 'Für alle einrichten',
|
||||
'assistant.connect.title': 'KI-Assistent',
|
||||
'assistant.connect.description':
|
||||
'Chatte mit einem Assistenten, der deine Reise kennt und Änderungen vorschlägt, die du bestätigst.',
|
||||
'assistant.connect.chatgpt': 'Mit ChatGPT anmelden',
|
||||
'assistant.connect.chatgptHint': 'Nutzt dein ChatGPT-Abo. Kein Schlüssel nötig.',
|
||||
'assistant.connect.chatgptStep1': 'Melde dich im neuen Tab bei ChatGPT an.',
|
||||
'assistant.connect.chatgptOpen': 'ChatGPT-Anmeldung öffnen',
|
||||
'assistant.connect.chatgptStep2':
|
||||
'Danach zeigt dein Browser eine Fehlerseite. Das ist richtig so. Kopiere die Adresse oben und komm zurück.',
|
||||
'assistant.connect.chatgptPaste': 'Adresse einfügen',
|
||||
'assistant.connect.chatgptWrong': 'Das ist nicht die richtige Adresse. Sie beginnt mit 127.0.0.1.',
|
||||
'assistant.connect.chatgptExpired': 'Das hat zu lange gedauert. Melde dich nochmal an.',
|
||||
'assistant.connect.chatgptOtherAccount': 'Das ist ein anderes ChatGPT-Konto. Entferne zuerst die Verbindung.',
|
||||
'assistant.connect.key': 'API-Schlüssel einfügen',
|
||||
'assistant.connect.keyPlaceholder': 'Schlüssel hier einfügen',
|
||||
'assistant.connect.detected': 'Erkannt: {provider}',
|
||||
'assistant.connect.whichProvider': 'Von welchem Anbieter ist der Schlüssel?',
|
||||
'assistant.connect.testing': 'Prüfe Verbindung',
|
||||
'assistant.connect.ok': 'Verbunden. Los geht’s.',
|
||||
'assistant.connect.openAssistant': 'Assistent öffnen',
|
||||
'assistant.connect.badKey': 'Dieser Schlüssel funktioniert nicht.',
|
||||
'assistant.connect.unknownModel': 'Dieses Modell wird hier nicht angeboten.',
|
||||
'assistant.connect.getKey': 'Schlüssel holen',
|
||||
'assistant.connect.hintAnthropic': 'Claude Max und Team enthalten API-Guthaben.',
|
||||
'assistant.connect.hintOpenai': 'Leg einen Schlüssel in deinem OpenAI-Dashboard an.',
|
||||
'assistant.connect.hintGemini': 'Im AI Studio gibt es eine Gratis-Stufe mit Limits pro Minute und pro Tag, in der Google deine Eingaben zur Verbesserung seiner Produkte nutzen darf. Mit aktivierter Abrechnung gelten höhere Limits, und das entfällt.',
|
||||
'assistant.connect.hintOpenrouter': 'Melde dich bei OpenRouter an, ganz ohne Schlüssel zu kopieren.',
|
||||
'assistant.connect.openrouter': 'Mit OpenRouter verbinden',
|
||||
'assistant.connect.save': 'Verbinden',
|
||||
'assistant.connect.model': 'Modell',
|
||||
'assistant.connect.modelHint': 'Zur Wahl stehen nur Modelle, die Werkzeuge nutzen können.',
|
||||
'assistant.connect.free': 'Gratis-Stufe verfügbar',
|
||||
'assistant.connect.paid': 'Nach Verbrauch',
|
||||
'assistant.model.recommended': 'Empfohlen',
|
||||
'assistant.model.free': 'Kostenlos',
|
||||
'assistant.connect.connected': 'Verbunden mit {provider}',
|
||||
'assistant.connect.providedByAdmin': 'Von deinem Admin bereitgestellt',
|
||||
'assistant.connect.disconnect': 'Trennen',
|
||||
'assistant.connect.disconnectConfirm': '{provider} trennen? Deine Unterhaltungen bleiben erhalten.',
|
||||
'assistant.connect.signOut': 'Abmelden',
|
||||
'assistant.connect.statusOk': 'Verbunden',
|
||||
'assistant.connect.statusNeedsReauth': 'Anmeldung abgelaufen',
|
||||
'assistant.connect.statusSignedOut': 'Abgemeldet',
|
||||
'assistant.connect.statusError': 'Nicht erreichbar',
|
||||
'assistant.connect.quota': 'Heute {used} von {count} Nachrichten genutzt',
|
||||
'assistant.connect.quota.one': 'Heute {used} von {count} Nachricht genutzt',
|
||||
'assistant.connect.claudeLink': 'Du nutzt Claude? In Claude verbinden',
|
||||
'assistant.privacy.what': 'Was wird geteilt?',
|
||||
'assistant.privacy.banner': 'Deine Nachrichten und die Trip-Daten, die der Assistent liest, gehen an {provider}.',
|
||||
'assistant.privacy.ok': 'Verstanden',
|
||||
'assistant.privacy.shared':
|
||||
'Deine Nachrichten und die Reisedaten, die der Assistent liest, etwa Orte, Tage und Buchungen.',
|
||||
'assistant.privacy.notShared': 'Nicht dein Passwort, nicht deine Dateien und nicht deine anderen Reisen.',
|
||||
'assistant.privacy.provider': '{provider} verarbeitet das nach den eigenen Datenschutzbedingungen.',
|
||||
'assistant.empty.title': 'Was planen wir?',
|
||||
'assistant.empty.readOnly': 'Du kannst diese Reise nur ansehen, deshalb beantworte ich Fragen, ändere aber nichts.',
|
||||
'assistant.suggest.planFirstDays': 'Plane mir die ersten Tage',
|
||||
'assistant.suggest.mustSee': 'Was sollte ich hier nicht verpassen?',
|
||||
'assistant.suggest.packingList': 'Erstelle eine Packliste für diese Reise',
|
||||
'assistant.suggest.emptyDays': 'Welche Tage sind noch leer?',
|
||||
'assistant.suggest.rainPlan': 'Was machen wir, wenn es regnet?',
|
||||
'assistant.suggest.today': 'Was steht heute an?',
|
||||
'assistant.suggest.eatNearby': 'Such mir etwas zum Essen in der Nähe',
|
||||
'assistant.suggest.weather': 'Wie wird das Wetter?',
|
||||
'assistant.suggest.openTodos': 'Was ist auf der To-do-Liste noch offen?',
|
||||
'assistant.suggest.settleUp': 'Wer schuldet wem was?',
|
||||
'assistant.suggest.recap': 'Fass unsere Reise zusammen',
|
||||
'assistant.suggest.nearPlace': 'Was gibt es noch in der Nähe von {place}?',
|
||||
'assistant.suggest.placeTips': 'Tipps für den Besuch von {place}',
|
||||
'assistant.suggest.planDay': 'Plane mir {day}',
|
||||
'assistant.suggest.dayTooFull': 'Ist {day} zu voll?',
|
||||
'assistant.thread.scrollDown': 'Zur neuesten Nachricht',
|
||||
'assistant.attach.button': 'Buchung anhängen',
|
||||
'assistant.attach.drop': 'Hier loslassen, dann lese ich die Buchung',
|
||||
'assistant.attach.unsupported': 'Diese Datei kann ich nicht lesen. Versuch es mit PDF, Mail, Bild oder Wallet-Pass.',
|
||||
'assistant.attach.tooLarge': 'Diese Datei ist größer als 10 MB.',
|
||||
'assistant.composer.placeholder': 'Frag etwas zu {trip}',
|
||||
'assistant.composer.send': 'Senden',
|
||||
'assistant.composer.stop': 'Stopp',
|
||||
'assistant.composer.decideFirst': 'Übernimm oder verwirf zuerst die Änderungen oben.',
|
||||
'assistant.context.ask': 'Fragen',
|
||||
'assistant.context.remove': 'Kontext entfernen',
|
||||
'assistant.step.reading': 'Liest deinen Plan',
|
||||
'assistant.step.searching': 'Sucht nach Orten',
|
||||
'assistant.step.details': 'Schlägt Details nach',
|
||||
'assistant.step.nearby': 'Sucht in der Nähe',
|
||||
'assistant.step.categories': 'Prüft die Kategorien',
|
||||
'assistant.step.weather': 'Prüft das Wetter',
|
||||
'assistant.step.todos': 'Liest die To-do-Liste',
|
||||
'assistant.step.costs': 'Prüft die Kosten',
|
||||
'assistant.step.help': 'Schaut in der Hilfe nach',
|
||||
'assistant.step.readingBooking': 'Liest die Buchung',
|
||||
'assistant.step.working': 'Arbeitet daran',
|
||||
'assistant.step.collapsed': '{count} Schritte',
|
||||
'assistant.step.collapsed.one': '{count} Schritt',
|
||||
'assistant.change.title': 'Vorschlag für deinen Trip',
|
||||
'assistant.change.doneTitle': 'Erledigt',
|
||||
'assistant.change.apply': 'Übernehmen',
|
||||
'assistant.change.applyN': '{count} übernehmen',
|
||||
'assistant.change.applyN.one': '{count} übernehmen',
|
||||
'assistant.change.discard': 'Verwerfen',
|
||||
'assistant.change.include': 'Diese Änderung einbeziehen',
|
||||
'assistant.change.applied': 'Übernommen',
|
||||
'assistant.change.discarded': 'Verworfen',
|
||||
'assistant.change.failed': 'Das ließ sich nicht übernehmen.',
|
||||
'assistant.change.applying': 'Wird übernommen',
|
||||
'assistant.change.expired': 'Nicht mehr verfügbar',
|
||||
'assistant.change.undo': 'Rückgängig',
|
||||
'assistant.change.undone': 'Rückgängig gemacht',
|
||||
'assistant.change.undoStale': 'Inzwischen geändert. Bitte pass es von Hand an.',
|
||||
'assistant.change.undoFailed': 'Das ließ sich nicht rückgängig machen.',
|
||||
'assistant.change.showOnMap': 'Auf Karte zeigen',
|
||||
'assistant.change.empty': 'leer',
|
||||
'assistant.change.pending': '{count} Änderungen warten auf deine Bestätigung',
|
||||
'assistant.change.pending.one': '{count} Änderung wartet auf deine Bestätigung',
|
||||
'assistant.change.verb.addPlace': 'Ort hinzufügen',
|
||||
'assistant.change.verb.addPlaceToDay': 'Ort zum Tag hinzufügen',
|
||||
'assistant.change.verb.updatePlace': 'Ort ändern',
|
||||
'assistant.change.verb.assignPlace': 'Ort an einem Tag einplanen',
|
||||
'assistant.change.verb.moveAssignment': 'Auf einen anderen Tag verschieben',
|
||||
'assistant.change.verb.removeFromDay': 'Vom Tag nehmen',
|
||||
'assistant.change.verb.addDayNote': 'Notiz hinzufügen',
|
||||
'assistant.change.verb.updateDayNote': 'Notiz ändern',
|
||||
'assistant.change.verb.addPackingItem': 'Zur Packliste hinzufügen',
|
||||
'assistant.change.verb.updatePackingItem': 'Packlisten-Eintrag ändern',
|
||||
'assistant.change.verb.addTodo': 'To-do hinzufügen',
|
||||
'assistant.change.verb.addBudgetItem': 'Ausgabe hinzufügen',
|
||||
'assistant.change.verb.addReservation': 'Buchung hinzufügen',
|
||||
'assistant.change.verb.importBooking': 'Buchung aus Datei hinzufügen',
|
||||
'assistant.change.label.name': 'Name', // same-as-en
|
||||
'assistant.change.label.description': 'Beschreibung',
|
||||
'assistant.change.label.address': 'Adresse',
|
||||
'assistant.change.label.coordinates': 'Position auf der Karte',
|
||||
'assistant.change.label.category': 'Kategorie',
|
||||
'assistant.change.label.day': 'Tag',
|
||||
'assistant.change.label.time': 'Uhrzeit',
|
||||
'assistant.change.label.endTime': 'Bis',
|
||||
'assistant.change.label.notes': 'Notizen',
|
||||
'assistant.change.label.website': 'Webseite',
|
||||
'assistant.change.label.phone': 'Telefon',
|
||||
'assistant.change.label.email': 'E-Mail',
|
||||
'assistant.change.label.openingHours': 'Öffnungszeiten',
|
||||
'assistant.change.label.price': 'Preis',
|
||||
'assistant.change.label.currency': 'Währung',
|
||||
'assistant.change.label.stopType': 'Art des Stopps',
|
||||
'assistant.change.label.text': 'Text', // same-as-en
|
||||
'assistant.change.label.icon': 'Symbol',
|
||||
'assistant.change.label.color': 'Farbe',
|
||||
'assistant.change.label.position': 'Reihenfolge',
|
||||
'assistant.change.label.quantity': 'Menge',
|
||||
'assistant.change.label.weight': 'Gewicht',
|
||||
'assistant.change.label.bag': 'Gepäckstück',
|
||||
'assistant.change.label.packed': 'Gepackt',
|
||||
'assistant.change.label.dueDate': 'Fällig',
|
||||
'assistant.change.label.priority': 'Priorität',
|
||||
'assistant.change.label.assignee': 'Zuständig',
|
||||
'assistant.change.label.type': 'Art',
|
||||
'assistant.change.label.location': 'Ort',
|
||||
'assistant.change.label.link': 'Link', // same-as-en
|
||||
'assistant.change.label.confirmation': 'Buchungsnummer',
|
||||
'assistant.change.label.checkIn': 'Check-in', // same-as-en
|
||||
'assistant.change.label.checkOut': 'Check-out', // same-as-en
|
||||
'assistant.change.label.stay': 'Aufenthalt',
|
||||
'assistant.change.label.members': 'Aufgeteilt auf',
|
||||
'assistant.change.label.route': 'Strecke',
|
||||
'assistant.history.open': 'Letzte Unterhaltungen',
|
||||
'assistant.history.new': 'Neue Unterhaltung',
|
||||
'assistant.history.empty': 'Noch keine Unterhaltungen.',
|
||||
'assistant.history.retention': 'Unterhaltungen werden nach {count} Tagen gelöscht.',
|
||||
'assistant.history.retention.one': 'Unterhaltungen werden nach {count} Tag gelöscht.',
|
||||
'assistant.history.forever': 'Unterhaltungen bleiben, bis du sie löschst.',
|
||||
'assistant.error.notConnected': 'Der Assistent ist noch nicht verbunden.',
|
||||
'assistant.error.busy': 'Läuft schon in einem anderen Fenster.',
|
||||
'assistant.error.dailyLimit': 'Für heute ist Schluss. Morgen geht es weiter.',
|
||||
'assistant.error.rateLimited': 'Etwas langsamer bitte. Versuch es in einer Minute nochmal.',
|
||||
'assistant.error.needsReauth': 'Deine Anmeldung bei {provider} ist abgelaufen.',
|
||||
'assistant.error.usageLimit': 'Dein ChatGPT-Kontingent ist aufgebraucht.',
|
||||
'assistant.error.notEligible': 'Dieses ChatGPT-Konto kann hier nicht genutzt werden.',
|
||||
'assistant.error.invalidKey': 'Dein Schlüssel für {provider} funktioniert nicht mehr.',
|
||||
'assistant.error.providerDown': '{provider} antwortet gerade nicht.',
|
||||
'assistant.error.maxSteps': 'Das war viel auf einmal. Soll ich weitermachen?',
|
||||
'assistant.error.offline': 'Offline. Der Assistent braucht Internet.',
|
||||
'assistant.error.noBookingsFound': 'In dieser Datei wurde keine Buchung gefunden.',
|
||||
'assistant.error.importUnavailable': 'Buchungsdateien lesen ist hier nicht verfügbar.',
|
||||
'assistant.error.generic': 'Etwas ist schiefgelaufen.',
|
||||
'assistant.errorAction.retry': 'Nochmal versuchen',
|
||||
'assistant.errorAction.continueHere': 'Hier weitermachen',
|
||||
'assistant.errorAction.continue': 'Weitermachen',
|
||||
'assistant.errorAction.signIn': 'Neu anmelden',
|
||||
'assistant.errorAction.viewUsage': 'Kontingent ansehen',
|
||||
'assistant.errorAction.otherProvider': 'Anderen Anbieter wählen',
|
||||
'assistant.errorAction.replaceKey': 'Schlüssel ersetzen',
|
||||
'assistant.errorAction.connectOwn': 'Eigenen Anbieter verbinden',
|
||||
'assistant.admin.policy': 'Wer zahlt',
|
||||
'assistant.admin.policy.instance': 'Ich zahle für alle',
|
||||
'assistant.admin.policy.instanceOrUser': 'Ich zahle, eigene erlaubt',
|
||||
'assistant.admin.policy.user': 'Jeder nutzt sein eigenes Konto',
|
||||
'assistant.admin.policy.instanceHint': 'Alle nutzen die Verbindung unten und können keine eigene hinzufügen.',
|
||||
'assistant.admin.policy.instanceOrUserHint': 'Alle nutzen die Verbindung unten, außer sie verbinden eine eigene.',
|
||||
'assistant.admin.policy.userHint': 'Jeder verbindet seinen eigenen Anbieter. Dir entstehen keine Kosten.',
|
||||
'assistant.admin.connection': 'Verbindung für alle',
|
||||
'assistant.admin.connectionNone': 'Noch keine Verbindung.',
|
||||
'assistant.admin.connectionRemove': 'Verbindung entfernen',
|
||||
'assistant.admin.custom': 'Eigener Server',
|
||||
'assistant.admin.customHint': 'Jeder OpenAI-kompatible Server, zum Beispiel Ollama.',
|
||||
'assistant.admin.baseUrl': 'Serveradresse',
|
||||
'assistant.admin.apiKeyOptional': 'API-Schlüssel (optional)',
|
||||
'assistant.admin.prefilled': 'Übernommen aus der KI-Auswertung.',
|
||||
'assistant.admin.dailyLimit': 'Nachrichten pro Person und Tag',
|
||||
'assistant.admin.dailyLimitHint': 'Zählt nur, solange du zahlst.',
|
||||
'assistant.admin.retention': 'Unterhaltungen aufbewahren',
|
||||
'assistant.admin.retentionDays': '{count} Tage',
|
||||
'assistant.admin.retentionDays.one': '{count} Tag',
|
||||
'assistant.admin.retentionForever': 'Für immer',
|
||||
'assistant.claude.title': 'Mit Claude verbinden',
|
||||
'assistant.claude.description': 'Nutze TREK direkt in Claude: als Connector hinzufügen, anmelden, fertig.',
|
||||
'assistant.claude.open': 'In Claude öffnen',
|
||||
'assistant.claude.scan': 'Oder mit dem Handy scannen',
|
||||
'assistant.claude.copyUrl': 'Adresse kopieren',
|
||||
'assistant.claude.qrAlt': 'QR-Code, um TREK in Claude hinzuzufügen',
|
||||
'assistant.claude.notPublic': 'Claude erreicht TREK nur, wenn es per https aus dem Internet erreichbar ist.',
|
||||
'assistant.mobile.tile': 'Frag TREK',
|
||||
'assistant.mobile.tileHint': 'Plane mit dem Assistenten',
|
||||
'assistant.mobile.backToChat': 'Zurück zum Chat',
|
||||
'assistant.step.nearbyAt': 'Sucht in der Nähe von {place}',
|
||||
'assistant.step.searchingFor': 'Sucht nach {query}',
|
||||
'assistant.step.weatherAt': 'Prüft das Wetter in {place}',
|
||||
'assistant.change.timeline.show': 'Ablauf zeigen',
|
||||
'assistant.change.timeline.hide': 'Ablauf ausblenden',
|
||||
'assistant.change.timeline.new': 'Neu',
|
||||
'assistant.change.timeline.travel': '≈ {count} Min. Weg',
|
||||
'assistant.change.timeline.travel.one': '≈ {count} Min. Weg',
|
||||
'assistant.change.timeline.gap': '{count} Min. frei',
|
||||
'assistant.change.timeline.gap.one': '{count} Min. frei',
|
||||
'assistant.change.timeline.overlap': 'Überschneidet sich mit {place}',
|
||||
'assistant.change.timeline.moreDays': '{count} weitere Tage',
|
||||
'assistant.change.timeline.moreDays.one': '{count} weiterer Tag',
|
||||
'assistant.change.viewOnMap': 'Auf Karte ansehen',
|
||||
'assistant.change.before': 'Vorher',
|
||||
'assistant.change.after': 'Nachher',
|
||||
'assistant.change.compare': 'Auf der Karte vergleichen',
|
||||
'assistant.change.undoAll': 'Alle rückgängig',
|
||||
'assistant.change.undoneAll': '{count} Änderungen rückgängig gemacht',
|
||||
'assistant.change.undoneAll.one': '{count} Änderung rückgängig gemacht',
|
||||
'assistant.change.undoAllPartial': '{done} rückgängig gemacht, {stale} inzwischen geändert',
|
||||
'assistant.change.undoAllFailed': '{count} Änderungen ließen sich nicht rückgängig machen',
|
||||
'assistant.change.undoAllFailed.one': '{count} Änderung ließ sich nicht rückgängig machen',
|
||||
'assistant.place.strip': 'Gefundene Orte',
|
||||
'assistant.place.addTo': 'Zu {day} hinzufügen',
|
||||
'assistant.place.addToShort': '+ Tag {n}',
|
||||
'assistant.place.pickDay': 'Tag wählen',
|
||||
'assistant.place.suggested': 'Vorgeschlagen',
|
||||
'assistant.place.inTrip': 'Schon im Trip',
|
||||
'assistant.place.showOnMap': 'Auf Karte',
|
||||
'assistant.place.openNow': 'Geöffnet',
|
||||
'assistant.place.closed': 'Geschlossen',
|
||||
'assistant.place.fromPrev': '{distance} vom vorigen Stopp',
|
||||
'assistant.place.more': '{count} weitere',
|
||||
'assistant.place.more.one': '{count} weiterer',
|
||||
'assistant.mention.days': 'Tage',
|
||||
'assistant.mention.places': 'Orte',
|
||||
'assistant.mention.bookings': 'Buchungen',
|
||||
'assistant.mention.none': 'Nichts gefunden',
|
||||
'assistant.mention.hint': 'Mit @ nennst du Tage, Orte und Buchungen.',
|
||||
'assistant.attach.dropPlace': 'Ort hier ablegen',
|
||||
'assistant.weather.strip': 'Wetter für {count} Tage',
|
||||
'assistant.weather.strip.one': 'Wetter für {count} Tag',
|
||||
'assistant.weather.rain': '{pct} % Regen',
|
||||
'assistant.settings.followMap': 'Karte folgt dem Assistenten',
|
||||
'assistant.settings.followMapHint': 'Die Karte schwenkt dorthin, wo der Assistent sucht und Orte vorschlägt.',
|
||||
'assistant.error.decideFirst': 'Entscheide zuerst über die offenen Vorschläge.',
|
||||
'assistant.error.providerLimit': 'Limit bei {provider} erreicht. Versuch es später noch einmal.',
|
||||
'assistant.hint.title': 'Aufgefallen',
|
||||
'assistant.hint.dismiss': 'Hinweis ausblenden',
|
||||
'assistant.hint.more': '{count} weitere',
|
||||
'assistant.hint.more.one': '{count} weiterer',
|
||||
'assistant.hint.overlap': '{a} und {b} überschneiden sich am {day}',
|
||||
'assistant.hint.overlap.prompt': 'Am {day} überschneiden sich {a} und {b}. Wie lösen wir das?',
|
||||
'assistant.hint.closed': '{place} hat am {day} geschlossen',
|
||||
'assistant.hint.closed.prompt': '{place} hat am {day} geschlossen. Was machen wir stattdessen?',
|
||||
'assistant.hint.opensLate': '{place} öffnet am {day} erst um {time}',
|
||||
'assistant.hint.closesEarly': '{place} schließt am {day} schon um {time}',
|
||||
'assistant.hint.outsideHours.prompt':
|
||||
'Unser Besuch in {place} am {day} passt nicht zu den Öffnungszeiten. Wie verschieben wir das am besten?',
|
||||
'assistant.hint.gap': '{day}: {count} Stunden frei ab {from}',
|
||||
'assistant.hint.gap.one': '{day}: {count} Stunde frei ab {from}',
|
||||
'assistant.hint.gap.prompt': 'Am {day} ist von {from} bis {to} nichts geplant. Was passt in die Lücke?',
|
||||
'assistant.hint.longLeg': '{from} und {to} liegen Luftlinie {distance} auseinander',
|
||||
'assistant.hint.longLeg.prompt': 'Ist der Weg von {from} nach {to} am {day} zu weit? Wie kommen wir am besten hin?',
|
||||
'assistant.hint.tightLeg': 'Zwischen {from} und {to} bleiben nur {count} Minuten',
|
||||
'assistant.hint.tightLeg.one': 'Zwischen {from} und {to} bleibt nur {count} Minute',
|
||||
'assistant.hint.tightLeg.prompt': 'Reicht die Zeit am {day} von {from} nach {to}? Was sollen wir anpassen?',
|
||||
'assistant.hint.noStay': 'Noch keine Unterkunft für {count} Nächte ab {day}',
|
||||
'assistant.hint.noStay.one': 'Noch keine Unterkunft für die Nacht nach {day}',
|
||||
'assistant.hint.noStay.prompt': 'Uns fehlt noch eine Unterkunft ab {day}. Was schlägst du vor?',
|
||||
'assistant.hint.unplacedBooking': '{title} hängt an keinem Tag',
|
||||
'assistant.hint.unplacedBookingDay': '{title} gehört wohl zum {day}',
|
||||
'assistant.hint.unplacedBookings': '{count} Buchungen hängen an keinem Tag',
|
||||
'assistant.hint.unplacedBookings.one': '{count} Buchung hängt an keinem Tag',
|
||||
'assistant.hint.unplacedBooking.prompt': 'Welche Buchungen hängen an keinem Tag, und wohin gehören sie?',
|
||||
'assistant.hint.menuOn': 'Hinweise zeigen',
|
||||
'assistant.hint.menuOff': 'Keine Hinweise zeigen',
|
||||
'assistant.launcher.withHints': 'Assistent, {count} Hinweise',
|
||||
'assistant.launcher.withHints.one': 'Assistent, {count} Hinweis',
|
||||
'assistant.mobile.tileHints': '{count} Hinweise zu deinem Plan',
|
||||
'assistant.mobile.tileHints.one': '{count} Hinweis zu deinem Plan',
|
||||
'assistant.share.button': 'In den Trip-Chat teilen',
|
||||
'assistant.share.title': 'In den Trip-Chat teilen',
|
||||
'assistant.share.who': 'Alle im Trip sehen diese Nachricht, markiert als vom Assistenten.',
|
||||
'assistant.share.includeChanges': 'Übernommene Änderungen anhängen',
|
||||
'assistant.share.changesHeading': 'Übernommen:',
|
||||
'assistant.share.truncated': 'Gekürzt auf {count} Zeichen',
|
||||
'assistant.share.truncated.one': 'Gekürzt auf {count} Zeichen',
|
||||
'assistant.share.send': 'Teilen',
|
||||
'assistant.share.done': 'Im Trip-Chat geteilt',
|
||||
'assistant.share.shared': 'Geteilt',
|
||||
'assistant.share.failed': 'Teilen hat nicht geklappt. Versuch es gleich noch mal.',
|
||||
'assistant.share.chatOff': 'Der Trip-Chat ist ausgeschaltet.',
|
||||
};
|
||||
export default assistant;
|
||||
@@ -106,5 +106,7 @@ const collab: TranslationStrings = {
|
||||
'collab.chat.attachImages': 'Bilder anhängen',
|
||||
'collab.chat.attachedImage': 'Angehängtes Bild',
|
||||
'collab.chat.closeImage': 'Bild schließen',
|
||||
'collab.chat.fromAssistant': 'vom Assistenten',
|
||||
'collab.chat.fromAssistantHint': '{name} hat diese Antwort des Assistenten geteilt',
|
||||
};
|
||||
export default collab;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import admin from './admin';
|
||||
import airport from './airport';
|
||||
import assistant from './assistant';
|
||||
import atlas from './atlas';
|
||||
import backup from './backup';
|
||||
import budget from './budget';
|
||||
@@ -116,5 +117,6 @@ const locale = {
|
||||
...dawarich,
|
||||
...docsync,
|
||||
...ferratas,
|
||||
...assistant,
|
||||
};
|
||||
export default locale;
|
||||
|
||||
@@ -86,6 +86,7 @@ const oauth: TranslationStrings = {
|
||||
'oauth.authorize.loginButton': 'Sign in to TREK', // en-fallback
|
||||
'oauth.authorize.requestLabel': 'Authorization Request', // en-fallback
|
||||
'oauth.authorize.requestDescription': 'This application is requesting access to your TREK account.', // en-fallback
|
||||
'oauth.authorize.verifiedHost': 'Bestätigte Domain: {host}',
|
||||
'oauth.authorize.trustNote': 'Only grant access to applications you trust. Your data stays on your server.', // en-fallback
|
||||
'oauth.authorize.selectScope': 'Select at least one scope', // en-fallback
|
||||
'oauth.authorize.approveScopes': 'Genehmigen ({count} Berechtigungen)',
|
||||
|
||||
@@ -688,6 +688,9 @@ const admin: TranslationStrings = {
|
||||
'admin.addons.catalog.llm_parsing.name': 'AI Parsing',
|
||||
'admin.addons.catalog.llm_parsing.description':
|
||||
'Reads bookings the built-in parser cannot, using an AI model you choose',
|
||||
'admin.addons.catalog.ai_assistant.name': 'AI Assistant',
|
||||
'admin.addons.catalog.ai_assistant.description':
|
||||
'A chat in every trip that answers questions and suggests changes you confirm',
|
||||
'admin.addons.llm.vision.auto': 'Automatic',
|
||||
'admin.addons.llm.vision.on': 'Yes',
|
||||
'admin.addons.llm.vision.off': 'No',
|
||||
|
||||
@@ -0,0 +1,339 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistant: TranslationStrings = {
|
||||
'assistant.launcher.tooltip': 'Assistant',
|
||||
'assistant.launcher.ctrlKey': 'Ctrl',
|
||||
'assistant.picker.title': 'Which trip?',
|
||||
'assistant.picker.empty': 'Create a trip first, then the assistant can help you plan it.',
|
||||
'assistant.picker.now': 'Now',
|
||||
'assistant.picker.next': 'Next',
|
||||
'assistant.window.title': 'Assistant',
|
||||
'assistant.window.log': 'Conversation with the assistant',
|
||||
'assistant.window.menu': 'More options',
|
||||
'assistant.window.settings': 'AI settings',
|
||||
'assistant.window.deleteThread': 'Delete chat',
|
||||
'assistant.window.deleteConfirm': 'Delete this chat? This cannot be undone.',
|
||||
'assistant.setup.question': 'What would you like to chat with?',
|
||||
'assistant.setup.beta': 'Beta',
|
||||
'assistant.setup.chatgptNote': 'An API key is the official way. Signing in with ChatGPT is still in beta.',
|
||||
'assistant.setup.keySubtitle': 'Anthropic, OpenAI, Gemini or OpenRouter',
|
||||
'assistant.setup.adminOnly': 'Your admin sets this up.',
|
||||
'assistant.setup.forEveryone': 'Set up for everyone',
|
||||
'assistant.connect.title': 'AI assistant',
|
||||
'assistant.connect.description': 'Chat with an assistant that knows your trip and suggests changes you confirm.',
|
||||
'assistant.connect.chatgpt': 'Sign in with ChatGPT',
|
||||
'assistant.connect.chatgptHint': 'Uses your ChatGPT plan. No key needed.',
|
||||
'assistant.connect.chatgptStep1': 'Sign in to ChatGPT in the new tab.',
|
||||
'assistant.connect.chatgptOpen': 'Open ChatGPT sign-in',
|
||||
'assistant.connect.chatgptStep2':
|
||||
'Your browser will then show an error page. That’s expected. Copy the address at the top and come back.',
|
||||
'assistant.connect.chatgptPaste': 'Paste address',
|
||||
'assistant.connect.chatgptWrong': 'That’s not the right address. It starts with 127.0.0.1.',
|
||||
'assistant.connect.chatgptExpired': 'That took too long. Please sign in again.',
|
||||
'assistant.connect.chatgptOtherAccount': 'This is a different ChatGPT account. Remove the connection first.',
|
||||
'assistant.connect.key': 'Paste an API key',
|
||||
'assistant.connect.keyPlaceholder': 'Paste your key here',
|
||||
'assistant.connect.detected': 'Detected: {provider}',
|
||||
'assistant.connect.whichProvider': 'Which provider is this key from?',
|
||||
'assistant.connect.testing': 'Checking connection',
|
||||
'assistant.connect.ok': 'Connected. Let’s go.',
|
||||
'assistant.connect.openAssistant': 'Open assistant',
|
||||
'assistant.connect.badKey': 'This key doesn’t work.',
|
||||
'assistant.connect.unknownModel': 'This model isn’t offered here.',
|
||||
'assistant.connect.getKey': 'Get a key',
|
||||
'assistant.connect.hintAnthropic': 'Claude Max and Team include API credits.',
|
||||
'assistant.connect.hintOpenai': 'Create a key in your OpenAI dashboard.',
|
||||
'assistant.connect.hintGemini': 'AI Studio has a free tier with limits per minute and per day, on which Google may use what you send to improve its products. With billing turned on the limits are higher and that does not apply.',
|
||||
'assistant.connect.hintOpenrouter': 'Sign in with OpenRouter, no key to copy.',
|
||||
'assistant.connect.openrouter': 'Connect OpenRouter',
|
||||
'assistant.connect.save': 'Connect',
|
||||
'assistant.connect.model': 'Model',
|
||||
'assistant.connect.modelHint': 'Only models that can use tools are listed.',
|
||||
'assistant.connect.free': 'Free tier available',
|
||||
'assistant.connect.paid': 'Pay as you go',
|
||||
'assistant.model.recommended': 'Recommended',
|
||||
'assistant.model.free': 'Free',
|
||||
'assistant.connect.connected': 'Connected to {provider}',
|
||||
'assistant.connect.providedByAdmin': 'Provided by your admin',
|
||||
'assistant.connect.disconnect': 'Disconnect',
|
||||
'assistant.connect.disconnectConfirm': 'Disconnect {provider}? Your chats stay.',
|
||||
'assistant.connect.signOut': 'Sign out',
|
||||
'assistant.connect.statusOk': 'Connected',
|
||||
'assistant.connect.statusNeedsReauth': 'Sign-in expired',
|
||||
'assistant.connect.statusSignedOut': 'Signed out',
|
||||
'assistant.connect.statusError': 'Not reachable',
|
||||
'assistant.connect.quota': '{used} of {count} messages used today',
|
||||
'assistant.connect.quota.one': '{used} of {count} message used today',
|
||||
'assistant.connect.claudeLink': 'Using Claude? Connect in Claude',
|
||||
'assistant.privacy.what': 'What gets shared?',
|
||||
'assistant.privacy.banner': 'Your messages and the trip data the assistant reads go to {provider}.',
|
||||
'assistant.privacy.ok': 'Got it',
|
||||
'assistant.privacy.shared': 'Your messages and the trip details the assistant reads, like places, days and bookings.',
|
||||
'assistant.privacy.notShared': 'Not your password, your files or your other trips.',
|
||||
'assistant.privacy.provider': '{provider} handles this under its own privacy terms.',
|
||||
'assistant.empty.title': 'What are we planning?',
|
||||
'assistant.empty.readOnly': 'You can only view this trip, so I can answer questions but not change anything.',
|
||||
'assistant.suggest.planFirstDays': 'Plan the first days for me',
|
||||
'assistant.suggest.mustSee': 'What shouldn’t I miss here?',
|
||||
'assistant.suggest.packingList': 'Make a packing list for this trip',
|
||||
'assistant.suggest.emptyDays': 'Which days are still empty?',
|
||||
'assistant.suggest.rainPlan': 'What can we do if it rains?',
|
||||
'assistant.suggest.today': 'What’s on today?',
|
||||
'assistant.suggest.eatNearby': 'Find somewhere to eat nearby',
|
||||
'assistant.suggest.weather': 'How’s the weather looking?',
|
||||
'assistant.suggest.openTodos': 'What’s still open on the to-do list?',
|
||||
'assistant.suggest.settleUp': 'Who owes whom?',
|
||||
'assistant.suggest.recap': 'Sum up our trip',
|
||||
'assistant.suggest.nearPlace': 'What else is near {place}?',
|
||||
'assistant.suggest.placeTips': 'Tips for visiting {place}',
|
||||
'assistant.suggest.planDay': 'Plan {day} for me',
|
||||
'assistant.suggest.dayTooFull': 'Is {day} too packed?',
|
||||
'assistant.thread.scrollDown': 'Jump to the latest message',
|
||||
'assistant.attach.button': 'Attach a booking',
|
||||
'assistant.attach.drop': 'Drop it here and I will read the booking',
|
||||
'assistant.attach.unsupported': 'This file type can’t be read. Try a PDF, email, image or wallet pass.',
|
||||
'assistant.attach.tooLarge': 'This file is larger than 10 MB.',
|
||||
'assistant.composer.placeholder': 'Ask anything about {trip}',
|
||||
'assistant.composer.send': 'Send',
|
||||
'assistant.composer.stop': 'Stop',
|
||||
'assistant.composer.decideFirst': 'Apply or discard the changes above first.',
|
||||
'assistant.context.ask': 'Ask',
|
||||
'assistant.context.remove': 'Remove context',
|
||||
'assistant.step.reading': 'Reading your plan',
|
||||
'assistant.step.searching': 'Searching for places',
|
||||
'assistant.step.details': 'Looking up details',
|
||||
'assistant.step.nearby': 'Searching nearby',
|
||||
'assistant.step.categories': 'Checking categories',
|
||||
'assistant.step.weather': 'Checking the weather',
|
||||
'assistant.step.todos': 'Reading the to-do list',
|
||||
'assistant.step.costs': 'Checking the costs',
|
||||
'assistant.step.help': 'Looking it up in the help',
|
||||
'assistant.step.readingBooking': 'Reading the booking',
|
||||
'assistant.step.working': 'Working on it',
|
||||
'assistant.step.collapsed': '{count} steps',
|
||||
'assistant.step.collapsed.one': '{count} step',
|
||||
'assistant.change.title': 'Suggested changes',
|
||||
'assistant.change.doneTitle': 'Done',
|
||||
'assistant.change.apply': 'Apply',
|
||||
'assistant.change.applyN': 'Apply {count}',
|
||||
'assistant.change.applyN.one': 'Apply {count}',
|
||||
'assistant.change.discard': 'Discard',
|
||||
'assistant.change.include': 'Include this change',
|
||||
'assistant.change.applied': 'Applied',
|
||||
'assistant.change.discarded': 'Discarded',
|
||||
'assistant.change.failed': 'Couldn’t apply this.',
|
||||
'assistant.change.applying': 'Applying',
|
||||
'assistant.change.expired': 'No longer available',
|
||||
'assistant.change.undo': 'Undo',
|
||||
'assistant.change.undone': 'Undone',
|
||||
'assistant.change.undoStale': 'Changed in the meantime. Please adjust it by hand.',
|
||||
'assistant.change.undoFailed': 'Couldn’t undo this.',
|
||||
'assistant.change.showOnMap': 'Show on map',
|
||||
'assistant.change.empty': 'empty',
|
||||
'assistant.change.pending': '{count} changes are waiting for you',
|
||||
'assistant.change.pending.one': '{count} change is waiting for you',
|
||||
'assistant.change.verb.addPlace': 'Add place',
|
||||
'assistant.change.verb.addPlaceToDay': 'Add place to day',
|
||||
'assistant.change.verb.updatePlace': 'Change place',
|
||||
'assistant.change.verb.assignPlace': 'Plan place on a day',
|
||||
'assistant.change.verb.moveAssignment': 'Move to another day',
|
||||
'assistant.change.verb.removeFromDay': 'Take off the day',
|
||||
'assistant.change.verb.addDayNote': 'Add note',
|
||||
'assistant.change.verb.updateDayNote': 'Change note',
|
||||
'assistant.change.verb.addPackingItem': 'Add to packing list',
|
||||
'assistant.change.verb.updatePackingItem': 'Change packing item',
|
||||
'assistant.change.verb.addTodo': 'Add to-do',
|
||||
'assistant.change.verb.addBudgetItem': 'Add expense',
|
||||
'assistant.change.verb.addReservation': 'Add booking',
|
||||
'assistant.change.verb.importBooking': 'Add booking from file',
|
||||
'assistant.change.label.name': 'Name',
|
||||
'assistant.change.label.description': 'Description',
|
||||
'assistant.change.label.address': 'Address',
|
||||
'assistant.change.label.coordinates': 'Location on map',
|
||||
'assistant.change.label.category': 'Category',
|
||||
'assistant.change.label.day': 'Day',
|
||||
'assistant.change.label.time': 'Time',
|
||||
'assistant.change.label.endTime': 'Until',
|
||||
'assistant.change.label.notes': 'Notes',
|
||||
'assistant.change.label.website': 'Website',
|
||||
'assistant.change.label.phone': 'Phone',
|
||||
'assistant.change.label.email': 'Email',
|
||||
'assistant.change.label.openingHours': 'Opening hours',
|
||||
'assistant.change.label.price': 'Price',
|
||||
'assistant.change.label.currency': 'Currency',
|
||||
'assistant.change.label.stopType': 'Stop type',
|
||||
'assistant.change.label.text': 'Text',
|
||||
'assistant.change.label.icon': 'Icon',
|
||||
'assistant.change.label.color': 'Colour',
|
||||
'assistant.change.label.position': 'Position',
|
||||
'assistant.change.label.quantity': 'Quantity',
|
||||
'assistant.change.label.weight': 'Weight',
|
||||
'assistant.change.label.bag': 'Bag',
|
||||
'assistant.change.label.packed': 'Packed',
|
||||
'assistant.change.label.dueDate': 'Due',
|
||||
'assistant.change.label.priority': 'Priority',
|
||||
'assistant.change.label.assignee': 'Assigned to',
|
||||
'assistant.change.label.type': 'Type',
|
||||
'assistant.change.label.location': 'Location',
|
||||
'assistant.change.label.link': 'Link',
|
||||
'assistant.change.label.confirmation': 'Confirmation number',
|
||||
'assistant.change.label.checkIn': 'Check-in',
|
||||
'assistant.change.label.checkOut': 'Check-out',
|
||||
'assistant.change.label.stay': 'Stay',
|
||||
'assistant.change.label.members': 'Split between',
|
||||
'assistant.change.label.route': 'Route',
|
||||
'assistant.history.open': 'Recent chats',
|
||||
'assistant.history.new': 'New chat',
|
||||
'assistant.history.empty': 'No chats yet.',
|
||||
'assistant.history.retention': 'Chats are deleted after {count} days.',
|
||||
'assistant.history.retention.one': 'Chats are deleted after {count} day.',
|
||||
'assistant.history.forever': 'Chats are kept until you delete them.',
|
||||
'assistant.error.notConnected': 'The assistant isn’t connected yet.',
|
||||
'assistant.error.busy': 'Already running in another window.',
|
||||
'assistant.error.dailyLimit': 'That’s it for today. See you tomorrow.',
|
||||
'assistant.error.rateLimited': 'Slow down a little. Try again in a minute.',
|
||||
'assistant.error.needsReauth': 'Your {provider} sign-in has expired.',
|
||||
'assistant.error.usageLimit': 'Your ChatGPT usage is used up.',
|
||||
'assistant.error.notEligible': 'This ChatGPT account can’t be used here.',
|
||||
'assistant.error.invalidKey': 'Your {provider} key doesn’t work anymore.',
|
||||
'assistant.error.providerDown': '{provider} isn’t responding right now.',
|
||||
'assistant.error.maxSteps': 'That was a lot at once. Shall I continue?',
|
||||
'assistant.error.offline': 'Offline. The assistant needs internet.',
|
||||
'assistant.error.noBookingsFound': 'No booking found in this file.',
|
||||
'assistant.error.importUnavailable': 'Reading booking files isn’t available here.',
|
||||
'assistant.error.generic': 'Something went wrong.',
|
||||
'assistant.errorAction.retry': 'Try again',
|
||||
'assistant.errorAction.continueHere': 'Continue here',
|
||||
'assistant.errorAction.continue': 'Continue',
|
||||
'assistant.errorAction.signIn': 'Sign in again',
|
||||
'assistant.errorAction.viewUsage': 'View usage',
|
||||
'assistant.errorAction.otherProvider': 'Choose another provider',
|
||||
'assistant.errorAction.replaceKey': 'Replace key',
|
||||
'assistant.errorAction.connectOwn': 'Connect your own provider',
|
||||
'assistant.admin.policy': 'Who pays',
|
||||
'assistant.admin.policy.instance': 'I pay for everyone',
|
||||
'assistant.admin.policy.instanceOrUser': 'I pay, own allowed',
|
||||
'assistant.admin.policy.user': 'Everyone uses their own account',
|
||||
'assistant.admin.policy.instanceHint': 'Everyone uses the connection below and can’t add their own.',
|
||||
'assistant.admin.policy.instanceOrUserHint': 'Everyone uses the connection below unless they connect their own.',
|
||||
'assistant.admin.policy.userHint': 'Everyone connects their own provider. Nothing is charged to you.',
|
||||
'assistant.admin.connection': 'Connection for everyone',
|
||||
'assistant.admin.connectionNone': 'No connection yet.',
|
||||
'assistant.admin.connectionRemove': 'Remove connection',
|
||||
'assistant.admin.custom': 'Own server',
|
||||
'assistant.admin.customHint': 'Any OpenAI-compatible server, for example Ollama.',
|
||||
'assistant.admin.baseUrl': 'Server address',
|
||||
'assistant.admin.apiKeyOptional': 'API key (optional)',
|
||||
'assistant.admin.prefilled': 'Taken over from AI Parsing.',
|
||||
'assistant.admin.dailyLimit': 'Messages per person per day',
|
||||
'assistant.admin.dailyLimitHint': 'Only counts while you pay.',
|
||||
'assistant.admin.retention': 'Keep chats',
|
||||
'assistant.admin.retentionDays': '{count} days',
|
||||
'assistant.admin.retentionDays.one': '{count} day',
|
||||
'assistant.admin.retentionForever': 'Forever',
|
||||
'assistant.claude.title': 'Connect with Claude',
|
||||
'assistant.claude.description': 'Use TREK right inside Claude: add it as a connector, sign in, done.',
|
||||
'assistant.claude.open': 'Open in Claude',
|
||||
'assistant.claude.scan': 'Or scan it with your phone',
|
||||
'assistant.claude.copyUrl': 'Copy address',
|
||||
'assistant.claude.qrAlt': 'QR code to add TREK in Claude',
|
||||
'assistant.claude.notPublic': 'Claude can only reach TREK when it is available over https on the internet.',
|
||||
'assistant.mobile.tile': 'Ask TREK',
|
||||
'assistant.mobile.tileHint': 'Plan with the assistant',
|
||||
'assistant.mobile.backToChat': 'Back to chat',
|
||||
'assistant.step.nearbyAt': 'Searching near {place}',
|
||||
'assistant.step.searchingFor': 'Looking for {query}',
|
||||
'assistant.step.weatherAt': 'Checking the weather in {place}',
|
||||
'assistant.change.timeline.show': 'Show schedule',
|
||||
'assistant.change.timeline.hide': 'Hide schedule',
|
||||
'assistant.change.timeline.new': 'New',
|
||||
'assistant.change.timeline.travel': '≈ {count} min travel',
|
||||
'assistant.change.timeline.travel.one': '≈ {count} min travel',
|
||||
'assistant.change.timeline.gap': '{count} min free',
|
||||
'assistant.change.timeline.gap.one': '{count} min free',
|
||||
'assistant.change.timeline.overlap': 'Overlaps with {place}',
|
||||
'assistant.change.timeline.moreDays': '{count} more days',
|
||||
'assistant.change.timeline.moreDays.one': '{count} more day',
|
||||
'assistant.change.viewOnMap': 'View on map',
|
||||
'assistant.change.before': 'Before',
|
||||
'assistant.change.after': 'After',
|
||||
'assistant.change.compare': 'Compare on the map',
|
||||
'assistant.change.undoAll': 'Undo all',
|
||||
'assistant.change.undoneAll': '{count} changes undone',
|
||||
'assistant.change.undoneAll.one': '{count} change undone',
|
||||
'assistant.change.undoAllPartial': '{done} undone, {stale} changed since',
|
||||
'assistant.change.undoAllFailed': '{count} changes could not be undone',
|
||||
'assistant.change.undoAllFailed.one': '{count} change could not be undone',
|
||||
'assistant.place.strip': 'Places found',
|
||||
'assistant.place.addTo': 'Add to {day}',
|
||||
'assistant.place.addToShort': '+ Day {n}',
|
||||
'assistant.place.pickDay': 'Pick a day',
|
||||
'assistant.place.suggested': 'Suggested',
|
||||
'assistant.place.inTrip': 'Already in your trip',
|
||||
'assistant.place.showOnMap': 'On map',
|
||||
'assistant.place.openNow': 'Open now',
|
||||
'assistant.place.closed': 'Closed',
|
||||
'assistant.place.fromPrev': '{distance} from the previous stop',
|
||||
'assistant.place.more': '{count} more',
|
||||
'assistant.place.more.one': '{count} more',
|
||||
'assistant.mention.days': 'Days',
|
||||
'assistant.mention.places': 'Places',
|
||||
'assistant.mention.bookings': 'Bookings',
|
||||
'assistant.mention.none': 'Nothing found',
|
||||
'assistant.mention.hint': 'Type @ to mention days, places and bookings.',
|
||||
'assistant.attach.dropPlace': 'Drop the place here',
|
||||
'assistant.weather.strip': 'Weather for {count} days',
|
||||
'assistant.weather.strip.one': 'Weather for {count} day',
|
||||
'assistant.weather.rain': '{pct}% rain',
|
||||
'assistant.settings.followMap': 'Map follows the assistant',
|
||||
'assistant.settings.followMapHint': 'The map pans to where the assistant searches and suggests places.',
|
||||
'assistant.error.decideFirst': 'Decide on the open suggestions first.',
|
||||
'assistant.error.providerLimit': 'Limit reached at {provider}. Try again later.',
|
||||
'assistant.hint.title': 'Worth a look',
|
||||
'assistant.hint.dismiss': 'Hide this hint',
|
||||
'assistant.hint.more': '{count} more',
|
||||
'assistant.hint.more.one': '{count} more',
|
||||
'assistant.hint.overlap': '{a} and {b} overlap on {day}',
|
||||
'assistant.hint.overlap.prompt': '{a} and {b} overlap on {day}. How should we sort that out?',
|
||||
'assistant.hint.closed': '{place} is closed on {day}',
|
||||
'assistant.hint.closed.prompt': '{place} is closed on {day}. What could we do instead?',
|
||||
'assistant.hint.opensLate': '{place} only opens at {time} on {day}',
|
||||
'assistant.hint.closesEarly': '{place} closes at {time} on {day}',
|
||||
'assistant.hint.outsideHours.prompt':
|
||||
'Our visit to {place} on {day} does not fit its opening hours. How should we move it?',
|
||||
'assistant.hint.gap': '{day}: {count} hours free from {from}',
|
||||
'assistant.hint.gap.one': '{day}: {count} hour free from {from}',
|
||||
'assistant.hint.gap.prompt': 'Nothing is planned on {day} from {from} to {to}. What would fit in there?',
|
||||
'assistant.hint.longLeg': '{from} and {to} are {distance} apart as the crow flies',
|
||||
'assistant.hint.longLeg.prompt': 'Is it too far from {from} to {to} on {day}? What is the best way to get there?',
|
||||
'assistant.hint.tightLeg': 'Only {count} minutes between {from} and {to}',
|
||||
'assistant.hint.tightLeg.one': 'Only {count} minute between {from} and {to}',
|
||||
'assistant.hint.tightLeg.prompt': 'Is there enough time on {day} to get from {from} to {to}? What should we change?',
|
||||
'assistant.hint.noStay': 'Nowhere to stay yet for {count} nights from {day}',
|
||||
'assistant.hint.noStay.one': 'Nowhere to stay yet for the night after {day}',
|
||||
'assistant.hint.noStay.prompt': 'We still need somewhere to stay from {day}. What do you suggest?',
|
||||
'assistant.hint.unplacedBooking': '{title} is not on any day yet',
|
||||
'assistant.hint.unplacedBookingDay': '{title} probably belongs on {day}',
|
||||
'assistant.hint.unplacedBookings': '{count} bookings are not on any day yet',
|
||||
'assistant.hint.unplacedBookings.one': '{count} booking is not on any day yet',
|
||||
'assistant.hint.unplacedBooking.prompt': 'Which bookings are not on a day yet, and where do they belong?',
|
||||
'assistant.hint.menuOn': 'Show hints',
|
||||
'assistant.hint.menuOff': 'Stop showing hints',
|
||||
'assistant.launcher.withHints': 'Assistant, {count} hints',
|
||||
'assistant.launcher.withHints.one': 'Assistant, {count} hint',
|
||||
'assistant.mobile.tileHints': '{count} things to check in your plan',
|
||||
'assistant.mobile.tileHints.one': '{count} thing to check in your plan',
|
||||
'assistant.share.button': 'Share in the trip chat',
|
||||
'assistant.share.title': 'Share in the trip chat',
|
||||
'assistant.share.who': 'Everyone on this trip will see this message, marked as from the assistant.',
|
||||
'assistant.share.includeChanges': 'Add the changes you applied',
|
||||
'assistant.share.changesHeading': 'Applied:',
|
||||
'assistant.share.truncated': 'Shortened to {count} characters',
|
||||
'assistant.share.truncated.one': 'Shortened to {count} character',
|
||||
'assistant.share.send': 'Share',
|
||||
'assistant.share.done': 'Shared in the trip chat',
|
||||
'assistant.share.shared': 'Shared',
|
||||
'assistant.share.failed': 'Could not share this. Please try again in a moment.',
|
||||
'assistant.share.chatOff': 'The trip chat is turned off.',
|
||||
};
|
||||
export default assistant;
|
||||
@@ -106,5 +106,7 @@ const collab: TranslationStrings = {
|
||||
'collab.chat.attachImages': 'Attach images',
|
||||
'collab.chat.attachedImage': 'Attached image',
|
||||
'collab.chat.closeImage': 'Close image',
|
||||
'collab.chat.fromAssistant': 'from the assistant',
|
||||
'collab.chat.fromAssistantHint': '{name} shared this answer from the assistant',
|
||||
};
|
||||
export default collab;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import admin from './admin';
|
||||
import airport from './airport';
|
||||
import assistant from './assistant';
|
||||
import atlas from './atlas';
|
||||
import backup from './backup';
|
||||
import budget from './budget';
|
||||
@@ -116,5 +117,6 @@ const locale = {
|
||||
...dawarich,
|
||||
...docsync,
|
||||
...ferratas,
|
||||
...assistant,
|
||||
};
|
||||
export default locale;
|
||||
|
||||
@@ -83,6 +83,7 @@ const oauth: TranslationStrings = {
|
||||
'oauth.authorize.loginButton': 'Sign in to TREK',
|
||||
'oauth.authorize.requestLabel': 'Authorization Request',
|
||||
'oauth.authorize.requestDescription': 'This application is requesting access to your TREK account.',
|
||||
'oauth.authorize.verifiedHost': 'Verified domain: {host}',
|
||||
'oauth.authorize.trustNote': 'Only grant access to applications you trust. Your data stays on your server.',
|
||||
'oauth.authorize.selectScope': 'Select at least one scope',
|
||||
'oauth.authorize.approveScopes': 'Approve ({count} scopes)',
|
||||
|
||||
@@ -625,6 +625,9 @@ const admin: TranslationStrings = {
|
||||
'admin.addons.catalog.llm_parsing.name': 'Análisis con IA',
|
||||
'admin.addons.catalog.llm_parsing.description':
|
||||
'Lee reservas que el analizador integrado no entiende, con un modelo de IA que tú elijas',
|
||||
'admin.addons.catalog.ai_assistant.name': 'Asistente de IA',
|
||||
'admin.addons.catalog.ai_assistant.description':
|
||||
'Un chat en cada viaje que responde preguntas y propone cambios que tú confirmas',
|
||||
'admin.addons.llm.vision.auto': 'Automático',
|
||||
'admin.addons.llm.vision.on': 'Sí',
|
||||
'admin.addons.llm.vision.off': 'No',
|
||||
|
||||
@@ -0,0 +1,340 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistant: TranslationStrings = {
|
||||
'assistant.launcher.tooltip': 'Asistente',
|
||||
'assistant.launcher.ctrlKey': 'Ctrl', // same-as-en
|
||||
'assistant.picker.title': '¿Qué viaje?',
|
||||
'assistant.picker.empty': 'Crea primero un viaje y el asistente te ayudará a planificarlo.',
|
||||
'assistant.picker.now': 'Ahora',
|
||||
'assistant.picker.next': 'Próximo',
|
||||
'assistant.window.title': 'Asistente',
|
||||
'assistant.window.log': 'Conversación con el asistente',
|
||||
'assistant.window.menu': 'Más opciones',
|
||||
'assistant.window.settings': 'Ajustes de IA',
|
||||
'assistant.window.deleteThread': 'Eliminar chat',
|
||||
'assistant.window.deleteConfirm': '¿Eliminar este chat? No se puede deshacer.',
|
||||
'assistant.setup.question': '¿Con qué quieres chatear?',
|
||||
'assistant.setup.beta': 'Beta', // same-as-en
|
||||
'assistant.setup.chatgptNote': 'Una clave API es la vía oficial. Iniciar sesión con ChatGPT sigue en beta.',
|
||||
'assistant.setup.keySubtitle': 'Anthropic, OpenAI, Gemini u OpenRouter',
|
||||
'assistant.setup.adminOnly': 'Lo configura tu administrador.',
|
||||
'assistant.setup.forEveryone': 'Configurar para todos',
|
||||
'assistant.connect.title': 'Asistente de IA',
|
||||
'assistant.connect.description': 'Chatea con un asistente que conoce tu viaje y propone cambios que tú confirmas.',
|
||||
'assistant.connect.chatgpt': 'Iniciar sesión con ChatGPT',
|
||||
'assistant.connect.chatgptHint': 'Usa tu plan de ChatGPT. Sin clave.',
|
||||
'assistant.connect.chatgptStep1': 'Inicia sesión en ChatGPT en la nueva pestaña.',
|
||||
'assistant.connect.chatgptOpen': 'Abrir inicio de sesión de ChatGPT',
|
||||
'assistant.connect.chatgptStep2':
|
||||
'Después, tu navegador mostrará una página de error. Es normal. Copia la dirección de arriba y vuelve aquí.',
|
||||
'assistant.connect.chatgptPaste': 'Pegar dirección',
|
||||
'assistant.connect.chatgptWrong': 'Esa no es la dirección correcta. Empieza por 127.0.0.1.',
|
||||
'assistant.connect.chatgptExpired': 'Ha tardado demasiado. Inicia sesión de nuevo.',
|
||||
'assistant.connect.chatgptOtherAccount': 'Es otra cuenta de ChatGPT. Elimina primero la conexión.',
|
||||
'assistant.connect.key': 'Pegar una clave API',
|
||||
'assistant.connect.keyPlaceholder': 'Pega tu clave aquí',
|
||||
'assistant.connect.detected': 'Detectado: {provider}',
|
||||
'assistant.connect.whichProvider': '¿De qué proveedor es esta clave?',
|
||||
'assistant.connect.testing': 'Comprobando la conexión',
|
||||
'assistant.connect.ok': 'Conectado. ¡Vamos!',
|
||||
'assistant.connect.openAssistant': 'Abrir asistente',
|
||||
'assistant.connect.badKey': 'Esta clave no funciona.',
|
||||
'assistant.connect.unknownModel': 'Este modelo no está disponible aquí.',
|
||||
'assistant.connect.getKey': 'Obtener una clave',
|
||||
'assistant.connect.hintAnthropic': 'Claude Max y Team incluyen créditos de API.',
|
||||
'assistant.connect.hintOpenai': 'Crea una clave en tu panel de OpenAI.',
|
||||
'assistant.connect.hintGemini': 'AI Studio tiene un nivel gratuito con límites por minuto y por día, en el que Google puede usar lo que envías para mejorar sus productos. Con la facturación activada, los límites son más altos y esto no se aplica.',
|
||||
'assistant.connect.hintOpenrouter': 'Inicia sesión con OpenRouter, sin clave que copiar.',
|
||||
'assistant.connect.openrouter': 'Conectar OpenRouter',
|
||||
'assistant.connect.save': 'Conectar',
|
||||
'assistant.connect.model': 'Modelo',
|
||||
'assistant.connect.modelHint': 'Solo se muestran los modelos que pueden usar herramientas.',
|
||||
'assistant.connect.free': 'Nivel gratuito disponible',
|
||||
'assistant.connect.paid': 'Pago por uso',
|
||||
'assistant.model.recommended': 'Recomendado',
|
||||
'assistant.model.free': 'Gratis',
|
||||
'assistant.connect.connected': 'Conectado a {provider}',
|
||||
'assistant.connect.providedByAdmin': 'Proporcionado por tu administrador',
|
||||
'assistant.connect.disconnect': 'Desconectar',
|
||||
'assistant.connect.disconnectConfirm': '¿Desconectar {provider}? Tus chats se conservan.',
|
||||
'assistant.connect.signOut': 'Cerrar sesión',
|
||||
'assistant.connect.statusOk': 'Conectado',
|
||||
'assistant.connect.statusNeedsReauth': 'Sesión caducada',
|
||||
'assistant.connect.statusSignedOut': 'Sesión cerrada',
|
||||
'assistant.connect.statusError': 'No disponible',
|
||||
'assistant.connect.quota': '{used} de {count} mensajes usados hoy',
|
||||
'assistant.connect.quota.one': '{used} de {count} mensaje usado hoy',
|
||||
'assistant.connect.claudeLink': '¿Usas Claude? Conéctate en Claude',
|
||||
'assistant.privacy.what': '¿Qué se comparte?',
|
||||
'assistant.privacy.banner': 'Tus mensajes y los datos del viaje que lee el asistente se envían a {provider}.',
|
||||
'assistant.privacy.ok': 'Entendido',
|
||||
'assistant.privacy.shared':
|
||||
'Tus mensajes y los detalles del viaje que lee el asistente, como lugares, días y reservas.',
|
||||
'assistant.privacy.notShared': 'Ni tu contraseña, ni tus archivos, ni tus otros viajes.',
|
||||
'assistant.privacy.provider': '{provider} los trata según sus propias condiciones de privacidad.',
|
||||
'assistant.empty.title': '¿Qué planificamos?',
|
||||
'assistant.empty.readOnly': 'Solo puedes ver este viaje, así que puedo responder preguntas pero no cambiar nada.',
|
||||
'assistant.suggest.planFirstDays': 'Planifícame los primeros días',
|
||||
'assistant.suggest.mustSee': '¿Qué no debería perderme aquí?',
|
||||
'assistant.suggest.packingList': 'Haz una lista de equipaje para este viaje',
|
||||
'assistant.suggest.emptyDays': '¿Qué días siguen vacíos?',
|
||||
'assistant.suggest.rainPlan': '¿Qué podemos hacer si llueve?',
|
||||
'assistant.suggest.today': '¿Qué hay hoy?',
|
||||
'assistant.suggest.eatNearby': 'Busca un sitio para comer cerca',
|
||||
'assistant.suggest.weather': '¿Qué tiempo va a hacer?',
|
||||
'assistant.suggest.openTodos': '¿Qué queda pendiente en la lista de tareas?',
|
||||
'assistant.suggest.settleUp': '¿Quién le debe a quién?',
|
||||
'assistant.suggest.recap': 'Resume nuestro viaje',
|
||||
'assistant.suggest.nearPlace': '¿Qué más hay cerca de {place}?',
|
||||
'assistant.suggest.placeTips': 'Consejos para visitar {place}',
|
||||
'assistant.suggest.planDay': 'Planifícame {day}',
|
||||
'assistant.suggest.dayTooFull': '¿{day} está demasiado lleno?',
|
||||
'assistant.thread.scrollDown': 'Ir al último mensaje',
|
||||
'assistant.attach.button': 'Adjuntar una reserva',
|
||||
'assistant.attach.drop': 'Suéltalo aquí y leeré la reserva',
|
||||
'assistant.attach.unsupported': 'No puedo leer este tipo de archivo. Prueba con un PDF, un correo, una imagen o un pase de cartera.',
|
||||
'assistant.attach.tooLarge': 'Este archivo supera los 10 MB.',
|
||||
'assistant.composer.placeholder': 'Pregunta lo que quieras sobre {trip}',
|
||||
'assistant.composer.send': 'Enviar',
|
||||
'assistant.composer.stop': 'Detener',
|
||||
'assistant.composer.decideFirst': 'Aplica o descarta primero los cambios de arriba.',
|
||||
'assistant.context.ask': 'Preguntar',
|
||||
'assistant.context.remove': 'Quitar contexto',
|
||||
'assistant.step.reading': 'Leyendo tu plan',
|
||||
'assistant.step.searching': 'Buscando lugares',
|
||||
'assistant.step.details': 'Consultando detalles',
|
||||
'assistant.step.nearby': 'Buscando cerca',
|
||||
'assistant.step.categories': 'Revisando categorías',
|
||||
'assistant.step.weather': 'Consultando el tiempo',
|
||||
'assistant.step.todos': 'Leyendo la lista de tareas',
|
||||
'assistant.step.costs': 'Revisando los gastos',
|
||||
'assistant.step.help': 'Buscando en la ayuda',
|
||||
'assistant.step.readingBooking': 'Leyendo la reserva',
|
||||
'assistant.step.working': 'Trabajando en ello',
|
||||
'assistant.step.collapsed': '{count} pasos',
|
||||
'assistant.step.collapsed.one': '{count} paso',
|
||||
'assistant.change.title': 'Cambios sugeridos',
|
||||
'assistant.change.doneTitle': 'Hecho',
|
||||
'assistant.change.apply': 'Aplicar',
|
||||
'assistant.change.applyN': 'Aplicar {count}',
|
||||
'assistant.change.applyN.one': 'Aplicar {count}',
|
||||
'assistant.change.discard': 'Descartar',
|
||||
'assistant.change.include': 'Incluir este cambio',
|
||||
'assistant.change.applied': 'Aplicado',
|
||||
'assistant.change.discarded': 'Descartado',
|
||||
'assistant.change.failed': 'No se pudo aplicar.',
|
||||
'assistant.change.applying': 'Aplicando',
|
||||
'assistant.change.expired': 'Ya no está disponible',
|
||||
'assistant.change.undo': 'Deshacer',
|
||||
'assistant.change.undone': 'Deshecho',
|
||||
'assistant.change.undoStale': 'Ha cambiado mientras tanto. Ajústalo a mano.',
|
||||
'assistant.change.undoFailed': 'No se pudo deshacer.',
|
||||
'assistant.change.showOnMap': 'Mostrar en el mapa',
|
||||
'assistant.change.empty': 'vacío',
|
||||
'assistant.change.pending': '{count} cambios te esperan',
|
||||
'assistant.change.pending.one': '{count} cambio te espera',
|
||||
'assistant.change.verb.addPlace': 'Añadir lugar',
|
||||
'assistant.change.verb.addPlaceToDay': 'Añadir lugar al día',
|
||||
'assistant.change.verb.updatePlace': 'Cambiar lugar',
|
||||
'assistant.change.verb.assignPlace': 'Planificar lugar en un día',
|
||||
'assistant.change.verb.moveAssignment': 'Mover a otro día',
|
||||
'assistant.change.verb.removeFromDay': 'Quitar del día',
|
||||
'assistant.change.verb.addDayNote': 'Añadir nota',
|
||||
'assistant.change.verb.updateDayNote': 'Cambiar nota',
|
||||
'assistant.change.verb.addPackingItem': 'Añadir a la lista de equipaje',
|
||||
'assistant.change.verb.updatePackingItem': 'Cambiar artículo de equipaje',
|
||||
'assistant.change.verb.addTodo': 'Añadir tarea',
|
||||
'assistant.change.verb.addBudgetItem': 'Añadir gasto',
|
||||
'assistant.change.verb.addReservation': 'Añadir reserva',
|
||||
'assistant.change.verb.importBooking': 'Añadir reserva desde archivo',
|
||||
'assistant.change.label.name': 'Nombre',
|
||||
'assistant.change.label.description': 'Descripción',
|
||||
'assistant.change.label.address': 'Dirección',
|
||||
'assistant.change.label.coordinates': 'Ubicación en el mapa',
|
||||
'assistant.change.label.category': 'Categoría',
|
||||
'assistant.change.label.day': 'Día',
|
||||
'assistant.change.label.time': 'Hora',
|
||||
'assistant.change.label.endTime': 'Hasta',
|
||||
'assistant.change.label.notes': 'Notas',
|
||||
'assistant.change.label.website': 'Sitio web',
|
||||
'assistant.change.label.phone': 'Teléfono',
|
||||
'assistant.change.label.email': 'Correo electrónico',
|
||||
'assistant.change.label.openingHours': 'Horario de apertura',
|
||||
'assistant.change.label.price': 'Precio',
|
||||
'assistant.change.label.currency': 'Moneda',
|
||||
'assistant.change.label.stopType': 'Tipo de parada',
|
||||
'assistant.change.label.text': 'Texto',
|
||||
'assistant.change.label.icon': 'Icono',
|
||||
'assistant.change.label.color': 'Color',
|
||||
'assistant.change.label.position': 'Orden',
|
||||
'assistant.change.label.quantity': 'Cantidad',
|
||||
'assistant.change.label.weight': 'Peso',
|
||||
'assistant.change.label.bag': 'Bolsa',
|
||||
'assistant.change.label.packed': 'Empacado',
|
||||
'assistant.change.label.dueDate': 'Vence',
|
||||
'assistant.change.label.priority': 'Prioridad',
|
||||
'assistant.change.label.assignee': 'Asignado a',
|
||||
'assistant.change.label.type': 'Tipo',
|
||||
'assistant.change.label.location': 'Ubicación',
|
||||
'assistant.change.label.link': 'Enlace',
|
||||
'assistant.change.label.confirmation': 'Número de confirmación',
|
||||
'assistant.change.label.checkIn': 'Entrada',
|
||||
'assistant.change.label.checkOut': 'Salida',
|
||||
'assistant.change.label.stay': 'Estancia',
|
||||
'assistant.change.label.members': 'Repartido entre',
|
||||
'assistant.change.label.route': 'Trayecto',
|
||||
'assistant.history.open': 'Chats recientes',
|
||||
'assistant.history.new': 'Nuevo chat',
|
||||
'assistant.history.empty': 'Aún no hay chats.',
|
||||
'assistant.history.retention': 'Los chats se eliminan después de {count} días.',
|
||||
'assistant.history.retention.one': 'Los chats se eliminan después de {count} día.',
|
||||
'assistant.history.forever': 'Los chats se conservan hasta que los elimines.',
|
||||
'assistant.error.notConnected': 'El asistente aún no está conectado.',
|
||||
'assistant.error.busy': 'Ya se está ejecutando en otra ventana.',
|
||||
'assistant.error.dailyLimit': 'Eso es todo por hoy. ¡Hasta mañana!',
|
||||
'assistant.error.rateLimited': 'Más despacio. Vuelve a intentarlo en un minuto.',
|
||||
'assistant.error.needsReauth': 'Tu sesión de {provider} ha caducado.',
|
||||
'assistant.error.usageLimit': 'Has agotado tu uso de ChatGPT.',
|
||||
'assistant.error.notEligible': 'Esta cuenta de ChatGPT no se puede usar aquí.',
|
||||
'assistant.error.invalidKey': 'Tu clave de {provider} ya no funciona.',
|
||||
'assistant.error.providerDown': '{provider} no responde en este momento.',
|
||||
'assistant.error.maxSteps': 'Ha sido mucho de golpe. ¿Sigo?',
|
||||
'assistant.error.offline': 'Sin conexión. El asistente necesita internet.',
|
||||
'assistant.error.noBookingsFound': 'No se encontró ninguna reserva en este archivo.',
|
||||
'assistant.error.importUnavailable': 'La lectura de archivos de reserva no está disponible aquí.',
|
||||
'assistant.error.generic': 'Algo ha salido mal.',
|
||||
'assistant.errorAction.retry': 'Reintentar',
|
||||
'assistant.errorAction.continueHere': 'Continuar aquí',
|
||||
'assistant.errorAction.continue': 'Continuar',
|
||||
'assistant.errorAction.signIn': 'Volver a iniciar sesión',
|
||||
'assistant.errorAction.viewUsage': 'Ver uso',
|
||||
'assistant.errorAction.otherProvider': 'Elegir otro proveedor',
|
||||
'assistant.errorAction.replaceKey': 'Reemplazar clave',
|
||||
'assistant.errorAction.connectOwn': 'Conectar tu propio proveedor',
|
||||
'assistant.admin.policy': 'Quién paga',
|
||||
'assistant.admin.policy.instance': 'Pago yo por todos',
|
||||
'assistant.admin.policy.instanceOrUser': 'Pago yo, se permite la propia',
|
||||
'assistant.admin.policy.user': 'Cada uno usa su propia cuenta',
|
||||
'assistant.admin.policy.instanceHint': 'Todos usan la conexión de abajo y no pueden añadir la suya.',
|
||||
'assistant.admin.policy.instanceOrUserHint': 'Todos usan la conexión de abajo, salvo que conecten la suya.',
|
||||
'assistant.admin.policy.userHint': 'Cada uno conecta su propio proveedor. No se te cobra nada.',
|
||||
'assistant.admin.connection': 'Conexión para todos',
|
||||
'assistant.admin.connectionNone': 'Aún no hay conexión.',
|
||||
'assistant.admin.connectionRemove': 'Eliminar conexión',
|
||||
'assistant.admin.custom': 'Servidor propio',
|
||||
'assistant.admin.customHint': 'Cualquier servidor compatible con OpenAI, por ejemplo Ollama.',
|
||||
'assistant.admin.baseUrl': 'Dirección del servidor',
|
||||
'assistant.admin.apiKeyOptional': 'Clave API (opcional)',
|
||||
'assistant.admin.prefilled': 'Tomado de Análisis con IA.',
|
||||
'assistant.admin.dailyLimit': 'Mensajes por persona y día',
|
||||
'assistant.admin.dailyLimitHint': 'Solo cuenta mientras pagas tú.',
|
||||
'assistant.admin.retention': 'Conservar chats',
|
||||
'assistant.admin.retentionDays': '{count} días',
|
||||
'assistant.admin.retentionDays.one': '{count} día',
|
||||
'assistant.admin.retentionForever': 'Para siempre',
|
||||
'assistant.claude.title': 'Conectar con Claude',
|
||||
'assistant.claude.description': 'Usa TREK directamente en Claude: añádelo como conector, inicia sesión y listo.',
|
||||
'assistant.claude.open': 'Abrir en Claude',
|
||||
'assistant.claude.scan': 'O escanéalo con tu móvil',
|
||||
'assistant.claude.copyUrl': 'Copiar dirección',
|
||||
'assistant.claude.qrAlt': 'Código QR para añadir TREK en Claude',
|
||||
'assistant.claude.notPublic': 'Claude solo puede acceder a TREK si está disponible por https en internet.',
|
||||
'assistant.mobile.tile': 'Pregunta a TREK',
|
||||
'assistant.mobile.tileHint': 'Planifica con el asistente',
|
||||
'assistant.mobile.backToChat': 'Volver al chat',
|
||||
'assistant.step.nearbyAt': 'Buscando cerca de {place}',
|
||||
'assistant.step.searchingFor': 'Buscando {query}',
|
||||
'assistant.step.weatherAt': 'Consultando el tiempo en {place}',
|
||||
'assistant.change.timeline.show': 'Ver horario',
|
||||
'assistant.change.timeline.hide': 'Ocultar horario',
|
||||
'assistant.change.timeline.new': 'Nuevo',
|
||||
'assistant.change.timeline.travel': '≈ {count} min de trayecto',
|
||||
'assistant.change.timeline.travel.one': '≈ {count} min de trayecto',
|
||||
'assistant.change.timeline.gap': '{count} min libres',
|
||||
'assistant.change.timeline.gap.one': '{count} min libre',
|
||||
'assistant.change.timeline.overlap': 'Se solapa con {place}',
|
||||
'assistant.change.timeline.moreDays': '{count} días más',
|
||||
'assistant.change.timeline.moreDays.one': '{count} día más',
|
||||
'assistant.change.viewOnMap': 'Ver en el mapa',
|
||||
'assistant.change.before': 'Antes',
|
||||
'assistant.change.after': 'Después',
|
||||
'assistant.change.compare': 'Comparar en el mapa',
|
||||
'assistant.change.undoAll': 'Deshacer todo',
|
||||
'assistant.change.undoneAll': '{count} cambios deshechos',
|
||||
'assistant.change.undoneAll.one': '{count} cambio deshecho',
|
||||
'assistant.change.undoAllPartial': 'Deshechos: {done}; modificados entretanto: {stale}',
|
||||
'assistant.change.undoAllFailed': 'No se pudieron deshacer {count} cambios',
|
||||
'assistant.change.undoAllFailed.one': 'No se pudo deshacer {count} cambio',
|
||||
'assistant.place.strip': 'Lugares encontrados',
|
||||
'assistant.place.addTo': 'Añadir a {day}',
|
||||
'assistant.place.addToShort': '+ Día {n}',
|
||||
'assistant.place.pickDay': 'Elegir día',
|
||||
'assistant.place.suggested': 'Propuesto',
|
||||
'assistant.place.inTrip': 'Ya está en tu viaje',
|
||||
'assistant.place.showOnMap': 'En el mapa',
|
||||
'assistant.place.openNow': 'Abierto ahora',
|
||||
'assistant.place.closed': 'Cerrado',
|
||||
'assistant.place.fromPrev': 'A {distance} de la parada anterior',
|
||||
'assistant.place.more': '{count} más',
|
||||
'assistant.place.more.one': '{count} más',
|
||||
'assistant.mention.days': 'Días',
|
||||
'assistant.mention.places': 'Lugares',
|
||||
'assistant.mention.bookings': 'Reservas',
|
||||
'assistant.mention.none': 'No hay resultados',
|
||||
'assistant.mention.hint': 'Escribe @ para mencionar días, lugares y reservas.',
|
||||
'assistant.attach.dropPlace': 'Suelta el lugar aquí',
|
||||
'assistant.weather.strip': 'Tiempo para {count} días',
|
||||
'assistant.weather.strip.one': 'Tiempo para {count} día',
|
||||
'assistant.weather.rain': '{pct} % de lluvia',
|
||||
'assistant.settings.followMap': 'El mapa sigue al asistente',
|
||||
'assistant.settings.followMapHint': 'El mapa se mueve hacia donde el asistente busca y propone lugares.',
|
||||
'assistant.error.decideFirst': 'Primero decide sobre las propuestas pendientes.',
|
||||
'assistant.error.providerLimit': 'Se alcanzó el límite en {provider}. Inténtalo más tarde.',
|
||||
'assistant.hint.title': 'Para revisar',
|
||||
'assistant.hint.dismiss': 'Ocultar este aviso',
|
||||
'assistant.hint.more': '{count} más',
|
||||
'assistant.hint.more.one': '{count} más',
|
||||
'assistant.hint.overlap': '{a} y {b} se solapan el {day}',
|
||||
'assistant.hint.overlap.prompt': 'El {day} se solapan {a} y {b}. ¿Cómo lo resolvemos?',
|
||||
'assistant.hint.closed': '{place} está cerrado el {day}',
|
||||
'assistant.hint.closed.prompt': '{place} está cerrado el {day}. ¿Qué hacemos en su lugar?',
|
||||
'assistant.hint.opensLate': '{place} no abre hasta las {time} el {day}',
|
||||
'assistant.hint.closesEarly': '{place} cierra ya a las {time} el {day}',
|
||||
'assistant.hint.outsideHours.prompt':
|
||||
'Nuestra visita a {place} el {day} no encaja con su horario. ¿Cómo la movemos mejor?',
|
||||
'assistant.hint.gap': '{day}: {count} horas libres desde las {from}',
|
||||
'assistant.hint.gap.one': '{day}: {count} hora libre desde las {from}',
|
||||
'assistant.hint.gap.prompt': 'El {day} no hay nada planeado de {from} a {to}. ¿Qué encajaría ahí?',
|
||||
'assistant.hint.longLeg': '{from} y {to} están a {distance} en línea recta',
|
||||
'assistant.hint.longLeg.prompt': '¿Está demasiado lejos {to} de {from} el {day}? ¿Cuál es la mejor forma de llegar?',
|
||||
'assistant.hint.tightLeg': 'Solo {count} minutos entre {from} y {to}',
|
||||
'assistant.hint.tightLeg.one': 'Solo {count} minuto entre {from} y {to}',
|
||||
'assistant.hint.tightLeg.prompt': '¿Da tiempo el {day} para ir de {from} a {to}? ¿Qué deberíamos cambiar?',
|
||||
'assistant.hint.noStay': 'Aún no hay alojamiento para {count} noches desde el {day}',
|
||||
'assistant.hint.noStay.one': 'Aún no hay alojamiento para {count} noche desde el {day}',
|
||||
'assistant.hint.noStay.prompt': 'Aún nos falta alojamiento desde el {day}. ¿Qué propones?',
|
||||
'assistant.hint.unplacedBooking': '{title} no está en ningún día',
|
||||
'assistant.hint.unplacedBookingDay': '{title} seguramente va el {day}',
|
||||
'assistant.hint.unplacedBookings': '{count} reservas no están en ningún día',
|
||||
'assistant.hint.unplacedBookings.one': '{count} reserva no está en ningún día',
|
||||
'assistant.hint.unplacedBooking.prompt': '¿Qué reservas no están en ningún día y dónde van?',
|
||||
'assistant.hint.menuOn': 'Mostrar avisos',
|
||||
'assistant.hint.menuOff': 'No mostrar avisos',
|
||||
'assistant.launcher.withHints': 'Asistente, {count} avisos',
|
||||
'assistant.launcher.withHints.one': 'Asistente, {count} aviso',
|
||||
'assistant.mobile.tileHints': '{count} cosas que revisar en tu plan',
|
||||
'assistant.mobile.tileHints.one': '{count} cosa que revisar en tu plan',
|
||||
'assistant.share.button': 'Compartir en el chat del viaje',
|
||||
'assistant.share.title': 'Compartir en el chat del viaje',
|
||||
'assistant.share.who': 'Todos en el viaje verán este mensaje, marcado como del asistente.',
|
||||
'assistant.share.includeChanges': 'Añadir los cambios aplicados',
|
||||
'assistant.share.changesHeading': 'Aplicado:',
|
||||
'assistant.share.truncated': 'Acortado a {count} caracteres',
|
||||
'assistant.share.truncated.one': 'Acortado a {count} carácter',
|
||||
'assistant.share.send': 'Compartir',
|
||||
'assistant.share.done': 'Compartido en el chat del viaje',
|
||||
'assistant.share.shared': 'Compartido',
|
||||
'assistant.share.failed': 'No se pudo compartir. Inténtalo de nuevo en un momento.',
|
||||
'assistant.share.chatOff': 'El chat del viaje está desactivado.',
|
||||
};
|
||||
export default assistant;
|
||||
@@ -106,5 +106,7 @@ const collab: TranslationStrings = {
|
||||
'collab.chat.attachImages': 'Adjuntar imágenes',
|
||||
'collab.chat.attachedImage': 'Imagen adjunta',
|
||||
'collab.chat.closeImage': 'Cerrar imagen',
|
||||
'collab.chat.fromAssistant': 'del asistente',
|
||||
'collab.chat.fromAssistantHint': '{name} compartió esta respuesta del asistente',
|
||||
};
|
||||
export default collab;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import admin from './admin';
|
||||
import airport from './airport';
|
||||
import assistant from './assistant';
|
||||
import atlas from './atlas';
|
||||
import backup from './backup';
|
||||
import budget from './budget';
|
||||
@@ -116,5 +117,6 @@ const locale = {
|
||||
...dawarich,
|
||||
...docsync,
|
||||
...ferratas,
|
||||
...assistant,
|
||||
};
|
||||
export default locale;
|
||||
|
||||
@@ -83,6 +83,7 @@ const oauth: TranslationStrings = {
|
||||
'oauth.authorize.loginButton': 'Sign in to TREK', // en-fallback
|
||||
'oauth.authorize.requestLabel': 'Authorization Request', // en-fallback
|
||||
'oauth.authorize.requestDescription': 'This application is requesting access to your TREK account.', // en-fallback
|
||||
'oauth.authorize.verifiedHost': 'Dominio verificado: {host}',
|
||||
'oauth.authorize.trustNote': 'Only grant access to applications you trust. Your data stays on your server.', // en-fallback
|
||||
'oauth.authorize.selectScope': 'Select at least one scope', // en-fallback
|
||||
'oauth.authorize.approveScopes': 'Aprobar ({count} ámbitos)',
|
||||
|
||||
@@ -706,6 +706,9 @@ const admin: TranslationStrings = {
|
||||
'admin.addons.catalog.llm_parsing.name': 'Tehisintellektiga parsimine',
|
||||
'admin.addons.catalog.llm_parsing.description':
|
||||
'Loeb sinu valitud tehisintellektimudeli abil broneeringuid, mida sisseehitatud parser lugeda ei suuda',
|
||||
'admin.addons.catalog.ai_assistant.name': 'Tehisintellekti assistent',
|
||||
'admin.addons.catalog.ai_assistant.description':
|
||||
'Vestlus igas reisis, mis vastab küsimustele ja pakub välja muudatusi, mille sina kinnitad',
|
||||
'admin.addons.llm.vision.auto': 'Automaatne',
|
||||
'admin.addons.llm.vision.on': 'Jah',
|
||||
'admin.addons.llm.vision.off': 'Ei',
|
||||
|
||||
@@ -0,0 +1,343 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistant: TranslationStrings = {
|
||||
'assistant.launcher.tooltip': 'Assistent',
|
||||
'assistant.launcher.ctrlKey': 'Ctrl', // same-as-en
|
||||
'assistant.picker.title': 'Milline reis?',
|
||||
'assistant.picker.empty': 'Loo kõigepealt reis, siis aitab assistent sul seda planeerida.',
|
||||
'assistant.picker.now': 'Praegu',
|
||||
'assistant.picker.next': 'Järgmine',
|
||||
'assistant.window.title': 'Assistent',
|
||||
'assistant.window.log': 'Vestlus assistendiga',
|
||||
'assistant.window.menu': 'Rohkem valikuid',
|
||||
'assistant.window.settings': 'Tehisintellekti seaded',
|
||||
'assistant.window.deleteThread': 'Kustuta vestlus',
|
||||
'assistant.window.deleteConfirm': 'Kas kustutada see vestlus? Seda ei saa tagasi võtta.',
|
||||
'assistant.setup.question': 'Millega soovid vestelda?',
|
||||
'assistant.setup.beta': 'Beeta',
|
||||
'assistant.setup.chatgptNote': 'API-võti on ametlik viis. ChatGPT-ga sisselogimine on veel beetaversioonis.',
|
||||
'assistant.setup.keySubtitle': 'Anthropic, OpenAI, Gemini või OpenRouter',
|
||||
'assistant.setup.adminOnly': 'Selle seadistab sinu administraator.',
|
||||
'assistant.setup.forEveryone': 'Seadista kõigile',
|
||||
'assistant.connect.title': 'Tehisintellekti assistent',
|
||||
'assistant.connect.description':
|
||||
'Vestle assistendiga, kes tunneb sinu reisi ja pakub välja muudatusi, mille sina kinnitad.',
|
||||
'assistant.connect.chatgpt': 'Logi sisse ChatGPT-ga',
|
||||
'assistant.connect.chatgptHint': 'Kasutab sinu ChatGPT paketti. Võtit pole vaja.',
|
||||
'assistant.connect.chatgptStep1': 'Logi uuel vahekaardil ChatGPT-sse sisse.',
|
||||
'assistant.connect.chatgptOpen': 'Ava ChatGPT sisselogimine',
|
||||
'assistant.connect.chatgptStep2':
|
||||
'Seejärel näitab brauser vealehte. See on ootuspärane. Kopeeri üleval olev aadress ja tule tagasi.',
|
||||
'assistant.connect.chatgptPaste': 'Kleebi aadress',
|
||||
'assistant.connect.chatgptWrong': 'See pole õige aadress. Õige algab 127.0.0.1-ga.',
|
||||
'assistant.connect.chatgptExpired': 'See võttis liiga kaua aega. Logi palun uuesti sisse.',
|
||||
'assistant.connect.chatgptOtherAccount': 'See on teine ChatGPT konto. Eemalda kõigepealt ühendus.',
|
||||
'assistant.connect.key': 'Kleebi API-võti',
|
||||
'assistant.connect.keyPlaceholder': 'Kleebi oma võti siia',
|
||||
'assistant.connect.detected': 'Tuvastatud: {provider}',
|
||||
'assistant.connect.whichProvider': 'Millise teenusepakkuja võti see on?',
|
||||
'assistant.connect.testing': 'Ühenduse kontrollimine',
|
||||
'assistant.connect.ok': 'Ühendatud. Läheme!',
|
||||
'assistant.connect.openAssistant': 'Ava assistent',
|
||||
'assistant.connect.badKey': 'See võti ei tööta.',
|
||||
'assistant.connect.unknownModel': 'Seda mudelit siin ei pakuta.',
|
||||
'assistant.connect.getKey': 'Hangi võti',
|
||||
'assistant.connect.hintAnthropic': 'Claude Max ja Team sisaldavad API-krediiti.',
|
||||
'assistant.connect.hintOpenai': 'Loo võti oma OpenAI juhtpaneelil.',
|
||||
'assistant.connect.hintGemini': 'AI Studios on minuti- ja päevapiirangutega tasuta tase, kus Google võib sinu saadetut oma toodete parandamiseks kasutada. Arveldusega on piirangud suuremad ja see ei kehti.',
|
||||
'assistant.connect.hintOpenrouter': 'Logi sisse OpenRouteriga, võtit pole vaja kopeerida.',
|
||||
'assistant.connect.openrouter': 'Ühenda OpenRouter',
|
||||
'assistant.connect.save': 'Ühenda',
|
||||
'assistant.connect.model': 'Mudel',
|
||||
'assistant.connect.modelHint': 'Loetletud on ainult mudelid, mis oskavad tööriistu kasutada.',
|
||||
'assistant.connect.free': 'Saadaval on tasuta tase',
|
||||
'assistant.connect.paid': 'Maksad kasutuse järgi',
|
||||
'assistant.model.recommended': 'Soovitatud',
|
||||
'assistant.model.free': 'Tasuta',
|
||||
'assistant.connect.connected': 'Ühendatud: {provider}',
|
||||
'assistant.connect.providedByAdmin': 'Pakub sinu administraator',
|
||||
'assistant.connect.disconnect': 'Katkesta ühendus',
|
||||
'assistant.connect.disconnectConfirm': 'Kas katkestada ühendus teenusega {provider}? Sinu vestlused jäävad alles.',
|
||||
'assistant.connect.signOut': 'Logi välja',
|
||||
'assistant.connect.statusOk': 'Ühendatud',
|
||||
'assistant.connect.statusNeedsReauth': 'Sisselogimine aegus',
|
||||
'assistant.connect.statusSignedOut': 'Välja logitud',
|
||||
'assistant.connect.statusError': 'Pole kättesaadav',
|
||||
'assistant.connect.quota': 'Täna kasutatud {used} sõnumit {count}-st',
|
||||
'assistant.connect.quota.one': 'Täna kasutatud {used} sõnumit {count}-st',
|
||||
'assistant.connect.claudeLink': 'Kasutad Claude’i? Ühenda Claude’is',
|
||||
'assistant.privacy.what': 'Mida jagatakse?',
|
||||
'assistant.privacy.banner': 'Sinu sõnumid ja reisiandmed, mida assistent loeb, saadetakse teenusele {provider}.',
|
||||
'assistant.privacy.ok': 'Selge',
|
||||
'assistant.privacy.shared':
|
||||
'Sinu sõnumid ja reisi üksikasjad, mida assistent loeb, näiteks kohad, päevad ja broneeringud.',
|
||||
'assistant.privacy.notShared': 'Mitte sinu parool, failid ega teised reisid.',
|
||||
'assistant.privacy.provider': '{provider} töötleb neid oma privaatsustingimuste järgi.',
|
||||
'assistant.empty.title': 'Mida me planeerime?',
|
||||
'assistant.empty.readOnly':
|
||||
'Sa saad seda reisi ainult vaadata, seega saan küsimustele vastata, kuid mitte midagi muuta.',
|
||||
'assistant.suggest.planFirstDays': 'Planeeri mulle esimesed päevad',
|
||||
'assistant.suggest.mustSee': 'Mida ma siin kindlasti nägema peaksin?',
|
||||
'assistant.suggest.packingList': 'Koosta selle reisi pakkimisnimekiri',
|
||||
'assistant.suggest.emptyDays': 'Millised päevad on veel tühjad?',
|
||||
'assistant.suggest.rainPlan': 'Mida saame teha, kui sajab?',
|
||||
'assistant.suggest.today': 'Mis täna kavas on?',
|
||||
'assistant.suggest.eatNearby': 'Leia lähedalt koht söömiseks',
|
||||
'assistant.suggest.weather': 'Milline ilm tuleb?',
|
||||
'assistant.suggest.openTodos': 'Mis on ülesannete nimekirjas veel tegemata?',
|
||||
'assistant.suggest.settleUp': 'Kes kellele võlgu on?',
|
||||
'assistant.suggest.recap': 'Tee meie reisist kokkuvõte',
|
||||
'assistant.suggest.nearPlace': 'Mis veel on {place} lähedal?',
|
||||
'assistant.suggest.placeTips': 'Nõuanded külastuseks: {place}',
|
||||
'assistant.suggest.planDay': 'Planeeri mulle {day}',
|
||||
'assistant.suggest.dayTooFull': 'Kas {day} on liiga täis?',
|
||||
'assistant.thread.scrollDown': 'Mine viimase sõnumi juurde',
|
||||
'assistant.attach.button': 'Lisa broneering',
|
||||
'assistant.attach.drop': 'Lase siin lahti ja ma loen broneeringu',
|
||||
'assistant.attach.unsupported': 'Seda failitüüpi ei saa lugeda. Proovi PDF-i, e-kirja, pilti või rahakotikaarti.',
|
||||
'assistant.attach.tooLarge': 'See fail on suurem kui 10 MB.',
|
||||
'assistant.composer.placeholder': 'Küsi midagi reisi {trip} kohta',
|
||||
'assistant.composer.send': 'Saada',
|
||||
'assistant.composer.stop': 'Peata',
|
||||
'assistant.composer.decideFirst': 'Rakenda või loobu kõigepealt ülalolevatest muudatustest.',
|
||||
'assistant.context.ask': 'Küsi',
|
||||
'assistant.context.remove': 'Eemalda kontekst',
|
||||
'assistant.step.reading': 'Loen sinu plaani',
|
||||
'assistant.step.searching': 'Otsin kohti',
|
||||
'assistant.step.details': 'Otsin üksikasju',
|
||||
'assistant.step.nearby': 'Otsin lähedalt',
|
||||
'assistant.step.categories': 'Kontrollin kategooriaid',
|
||||
'assistant.step.weather': 'Kontrollin ilma',
|
||||
'assistant.step.todos': 'Loen ülesannete nimekirja',
|
||||
'assistant.step.costs': 'Kontrollin kulusid',
|
||||
'assistant.step.help': 'Otsin abist',
|
||||
'assistant.step.readingBooking': 'Loen broneeringut',
|
||||
'assistant.step.working': 'Töötan selle kallal',
|
||||
'assistant.step.collapsed': '{count} sammu',
|
||||
'assistant.step.collapsed.one': '{count} samm',
|
||||
'assistant.change.title': 'Soovitatud muudatused',
|
||||
'assistant.change.doneTitle': 'Valmis',
|
||||
'assistant.change.apply': 'Rakenda',
|
||||
'assistant.change.applyN': 'Rakenda {count}',
|
||||
'assistant.change.applyN.one': 'Rakenda {count}',
|
||||
'assistant.change.discard': 'Loobu',
|
||||
'assistant.change.include': 'Kaasa see muudatus',
|
||||
'assistant.change.applied': 'Rakendatud',
|
||||
'assistant.change.discarded': 'Loobutud',
|
||||
'assistant.change.failed': 'Seda ei õnnestunud rakendada.',
|
||||
'assistant.change.applying': 'Rakendan',
|
||||
'assistant.change.expired': 'Pole enam saadaval',
|
||||
'assistant.change.undo': 'Võta tagasi',
|
||||
'assistant.change.undone': 'Tagasi võetud',
|
||||
'assistant.change.undoStale': 'Vahepeal muudetud. Kohanda palun käsitsi.',
|
||||
'assistant.change.undoFailed': 'Seda ei õnnestunud tagasi võtta.',
|
||||
'assistant.change.showOnMap': 'Näita kaardil',
|
||||
'assistant.change.empty': 'tühi',
|
||||
'assistant.change.pending': '{count} muudatust ootab sind',
|
||||
'assistant.change.pending.one': '{count} muudatus ootab sind',
|
||||
'assistant.change.verb.addPlace': 'Lisa koht',
|
||||
'assistant.change.verb.addPlaceToDay': 'Lisa koht päevale',
|
||||
'assistant.change.verb.updatePlace': 'Muuda kohta',
|
||||
'assistant.change.verb.assignPlace': 'Planeeri koht päevale',
|
||||
'assistant.change.verb.moveAssignment': 'Teisalda teisele päevale',
|
||||
'assistant.change.verb.removeFromDay': 'Eemalda päevast',
|
||||
'assistant.change.verb.addDayNote': 'Lisa märge',
|
||||
'assistant.change.verb.updateDayNote': 'Muuda märget',
|
||||
'assistant.change.verb.addPackingItem': 'Lisa pakkimisnimekirja',
|
||||
'assistant.change.verb.updatePackingItem': 'Muuda pakitavat eset',
|
||||
'assistant.change.verb.addTodo': 'Lisa ülesanne',
|
||||
'assistant.change.verb.addBudgetItem': 'Lisa kulu',
|
||||
'assistant.change.verb.addReservation': 'Lisa broneering',
|
||||
'assistant.change.verb.importBooking': 'Lisa broneering failist',
|
||||
'assistant.change.label.name': 'Nimi',
|
||||
'assistant.change.label.description': 'Kirjeldus',
|
||||
'assistant.change.label.address': 'Aadress',
|
||||
'assistant.change.label.coordinates': 'Asukoht kaardil',
|
||||
'assistant.change.label.category': 'Kategooria',
|
||||
'assistant.change.label.day': 'Päev',
|
||||
'assistant.change.label.time': 'Kellaaeg',
|
||||
'assistant.change.label.endTime': 'Kuni',
|
||||
'assistant.change.label.notes': 'Märkmed',
|
||||
'assistant.change.label.website': 'Veebisait',
|
||||
'assistant.change.label.phone': 'Telefon',
|
||||
'assistant.change.label.email': 'E-post',
|
||||
'assistant.change.label.openingHours': 'Lahtiolekuajad',
|
||||
'assistant.change.label.price': 'Hind',
|
||||
'assistant.change.label.currency': 'Valuuta',
|
||||
'assistant.change.label.stopType': 'Peatuse tüüp',
|
||||
'assistant.change.label.text': 'Tekst',
|
||||
'assistant.change.label.icon': 'Ikoon',
|
||||
'assistant.change.label.color': 'Värv',
|
||||
'assistant.change.label.position': 'Järjekord',
|
||||
'assistant.change.label.quantity': 'Kogus',
|
||||
'assistant.change.label.weight': 'Kaal',
|
||||
'assistant.change.label.bag': 'Kott',
|
||||
'assistant.change.label.packed': 'Pakitud',
|
||||
'assistant.change.label.dueDate': 'Tähtaeg',
|
||||
'assistant.change.label.priority': 'Prioriteet',
|
||||
'assistant.change.label.assignee': 'Määratud',
|
||||
'assistant.change.label.type': 'Tüüp',
|
||||
'assistant.change.label.location': 'Asukoht',
|
||||
'assistant.change.label.link': 'Link', // same-as-en
|
||||
'assistant.change.label.confirmation': 'Kinnituse number',
|
||||
'assistant.change.label.checkIn': 'Sisseregistreerimine',
|
||||
'assistant.change.label.checkOut': 'Väljaregistreerimine',
|
||||
'assistant.change.label.stay': 'Majutus',
|
||||
'assistant.change.label.members': 'Jagatud',
|
||||
'assistant.change.label.route': 'Marsruut',
|
||||
'assistant.history.open': 'Viimased vestlused',
|
||||
'assistant.history.new': 'Uus vestlus',
|
||||
'assistant.history.empty': 'Vestlusi veel pole.',
|
||||
'assistant.history.retention': 'Vestlused kustutatakse {count} päeva pärast.',
|
||||
'assistant.history.retention.one': 'Vestlused kustutatakse {count} päeva pärast.',
|
||||
'assistant.history.forever': 'Vestlused jäävad alles, kuni sa need kustutad.',
|
||||
'assistant.error.notConnected': 'Assistent pole veel ühendatud.',
|
||||
'assistant.error.busy': 'Juba töös teises aknas.',
|
||||
'assistant.error.dailyLimit': 'Tänaseks on kõik. Kohtumiseni homme.',
|
||||
'assistant.error.rateLimited': 'Natuke aeglasemalt. Proovi minuti pärast uuesti.',
|
||||
'assistant.error.needsReauth': 'Sinu sisselogimine teenusesse {provider} on aegunud.',
|
||||
'assistant.error.usageLimit': 'Sinu ChatGPT kasutuslimiit on täis.',
|
||||
'assistant.error.notEligible': 'Seda ChatGPT kontot ei saa siin kasutada.',
|
||||
'assistant.error.invalidKey': 'Sinu {provider} võti ei tööta enam.',
|
||||
'assistant.error.providerDown': '{provider} ei vasta praegu.',
|
||||
'assistant.error.maxSteps': 'Seda oli korraga palju. Kas jätkan?',
|
||||
'assistant.error.offline': 'Võrguühendus puudub. Assistent vajab internetti.',
|
||||
'assistant.error.noBookingsFound': 'Sellest failist ei leitud ühtegi broneeringut.',
|
||||
'assistant.error.importUnavailable': 'Broneeringufailide lugemine pole siin saadaval.',
|
||||
'assistant.error.generic': 'Midagi läks valesti.',
|
||||
'assistant.errorAction.retry': 'Proovi uuesti',
|
||||
'assistant.errorAction.continueHere': 'Jätka siin',
|
||||
'assistant.errorAction.continue': 'Jätka',
|
||||
'assistant.errorAction.signIn': 'Logi uuesti sisse',
|
||||
'assistant.errorAction.viewUsage': 'Vaata kasutust',
|
||||
'assistant.errorAction.otherProvider': 'Vali teine teenusepakkuja',
|
||||
'assistant.errorAction.replaceKey': 'Asenda võti',
|
||||
'assistant.errorAction.connectOwn': 'Ühenda oma teenusepakkuja',
|
||||
'assistant.admin.policy': 'Kes maksab',
|
||||
'assistant.admin.policy.instance': 'Mina maksan kõigi eest',
|
||||
'assistant.admin.policy.instanceOrUser': 'Mina maksan, oma lubatud',
|
||||
'assistant.admin.policy.user': 'Igaüks kasutab oma kontot',
|
||||
'assistant.admin.policy.instanceHint': 'Kõik kasutavad allolevat ühendust ega saa oma lisada.',
|
||||
'assistant.admin.policy.instanceOrUserHint': 'Kõik kasutavad allolevat ühendust, kui nad oma ei ühenda.',
|
||||
'assistant.admin.policy.userHint': 'Igaüks ühendab oma teenusepakkuja. Sinult ei võeta midagi.',
|
||||
'assistant.admin.connection': 'Ühendus kõigile',
|
||||
'assistant.admin.connectionNone': 'Ühendust veel pole.',
|
||||
'assistant.admin.connectionRemove': 'Eemalda ühendus',
|
||||
'assistant.admin.custom': 'Oma server',
|
||||
'assistant.admin.customHint': 'Mis tahes OpenAI-ga ühilduv server, näiteks Ollama.',
|
||||
'assistant.admin.baseUrl': 'Serveri aadress',
|
||||
'assistant.admin.apiKeyOptional': 'API-võti (valikuline)',
|
||||
'assistant.admin.prefilled': 'Üle võetud tehisintellektiga parsimisest.',
|
||||
'assistant.admin.dailyLimit': 'Sõnumeid inimese kohta päevas',
|
||||
'assistant.admin.dailyLimitHint': 'Loeb ainult siis, kui maksad sina.',
|
||||
'assistant.admin.retention': 'Vestluste säilitamine',
|
||||
'assistant.admin.retentionDays': '{count} päeva',
|
||||
'assistant.admin.retentionDays.one': '{count} päev',
|
||||
'assistant.admin.retentionForever': 'Igavesti',
|
||||
'assistant.claude.title': 'Ühenda Claude’iga',
|
||||
'assistant.claude.description': 'Kasuta TREKi otse Claude’is: lisa see konnektorina, logi sisse ja valmis.',
|
||||
'assistant.claude.open': 'Ava Claude’is',
|
||||
'assistant.claude.scan': 'Või skanni see telefoniga',
|
||||
'assistant.claude.copyUrl': 'Kopeeri aadress',
|
||||
'assistant.claude.qrAlt': 'QR-kood TREKi lisamiseks Claude’i',
|
||||
'assistant.claude.notPublic': 'Claude jõuab TREKini ainult siis, kui see on internetis https-i kaudu kättesaadav.',
|
||||
'assistant.mobile.tile': 'Küsi TREKilt',
|
||||
'assistant.mobile.tileHint': 'Planeeri assistendiga',
|
||||
'assistant.mobile.backToChat': 'Tagasi vestlusse',
|
||||
'assistant.step.nearbyAt': 'Otsin {place} lähedalt',
|
||||
'assistant.step.searchingFor': 'Otsin: {query}',
|
||||
'assistant.step.weatherAt': 'Vaatan ilma kohas {place}',
|
||||
'assistant.change.timeline.show': 'Näita ajakava',
|
||||
'assistant.change.timeline.hide': 'Peida ajakava',
|
||||
'assistant.change.timeline.new': 'Uus',
|
||||
'assistant.change.timeline.travel': '≈ {count} min teed',
|
||||
'assistant.change.timeline.travel.one': '≈ {count} min teed',
|
||||
'assistant.change.timeline.gap': '{count} min vaba',
|
||||
'assistant.change.timeline.gap.one': '{count} min vaba',
|
||||
'assistant.change.timeline.overlap': 'Kattub kohaga {place}',
|
||||
'assistant.change.timeline.moreDays': 'Veel {count} päeva',
|
||||
'assistant.change.timeline.moreDays.one': 'Veel {count} päev',
|
||||
'assistant.change.viewOnMap': 'Vaata kaardil',
|
||||
'assistant.change.before': 'Enne',
|
||||
'assistant.change.after': 'Pärast',
|
||||
'assistant.change.compare': 'Võrdle kaardil',
|
||||
'assistant.change.undoAll': 'Võta kõik tagasi',
|
||||
'assistant.change.undoneAll': '{count} muudatust tagasi võetud',
|
||||
'assistant.change.undoneAll.one': '{count} muudatus tagasi võetud',
|
||||
'assistant.change.undoAllPartial': 'Tagasi võetud: {done}; vahepeal muudetud: {stale}',
|
||||
'assistant.change.undoAllFailed': '{count} muudatust ei õnnestunud tagasi võtta',
|
||||
'assistant.change.undoAllFailed.one': '{count} muudatust ei õnnestunud tagasi võtta',
|
||||
'assistant.place.strip': 'Leitud kohad',
|
||||
'assistant.place.addTo': 'Lisa päevale {day}',
|
||||
'assistant.place.addToShort': '+ Päev {n}',
|
||||
'assistant.place.pickDay': 'Vali päev',
|
||||
'assistant.place.suggested': 'Soovitatud',
|
||||
'assistant.place.inTrip': 'Juba sinu reisil',
|
||||
'assistant.place.showOnMap': 'Kaardil',
|
||||
'assistant.place.openNow': 'Praegu avatud',
|
||||
'assistant.place.closed': 'Suletud',
|
||||
'assistant.place.fromPrev': '{distance} eelmisest peatusest',
|
||||
'assistant.place.more': 'Veel {count}',
|
||||
'assistant.place.more.one': 'Veel {count}',
|
||||
'assistant.mention.days': 'Päevad',
|
||||
'assistant.mention.places': 'Kohad',
|
||||
'assistant.mention.bookings': 'Broneeringud',
|
||||
'assistant.mention.none': 'Midagi ei leitud',
|
||||
'assistant.mention.hint': 'Kirjuta @, et mainida päevi, kohti ja broneeringuid.',
|
||||
'assistant.attach.dropPlace': 'Lohista koht siia',
|
||||
'assistant.weather.strip': 'Ilm {count} päevaks',
|
||||
'assistant.weather.strip.one': 'Ilm {count} päevaks',
|
||||
'assistant.weather.rain': 'Vihma {pct}%',
|
||||
'assistant.settings.followMap': 'Kaart järgib assistenti',
|
||||
'assistant.settings.followMapHint': 'Kaart liigub sinna, kus assistent otsib ja kohti soovitab.',
|
||||
'assistant.error.decideFirst': 'Otsusta esmalt avatud ettepanekute üle.',
|
||||
'assistant.error.providerLimit': 'Teenuse {provider} limiit on täis. Proovi hiljem uuesti.',
|
||||
'assistant.hint.title': 'Tasub vaadata',
|
||||
'assistant.hint.dismiss': 'Peida see vihje',
|
||||
'assistant.hint.more': 'Veel {count}',
|
||||
'assistant.hint.more.one': 'Veel {count}',
|
||||
'assistant.hint.overlap': '{a} ja {b} kattuvad päeval {day}',
|
||||
'assistant.hint.overlap.prompt': 'Päeval {day} kattuvad {a} ja {b}. Kuidas me selle lahendame?',
|
||||
'assistant.hint.closed': '{place} on päeval {day} suletud',
|
||||
'assistant.hint.closed.prompt': '{place} on päeval {day} suletud. Mida teeme selle asemel?',
|
||||
'assistant.hint.opensLate': '{place} avatakse päeval {day} alles kell {time}',
|
||||
'assistant.hint.closesEarly': '{place} suletakse päeval {day} juba kell {time}',
|
||||
'assistant.hint.outsideHours.prompt':
|
||||
'Meie külastus kohta {place} päeval {day} ei sobi lahtiolekuaegadega. Kuidas seda kõige paremini nihutada?',
|
||||
'assistant.hint.gap': '{day}: {count} tundi vaba alates {from}',
|
||||
'assistant.hint.gap.one': '{day}: {count} tund vaba alates {from}',
|
||||
'assistant.hint.gap.prompt': 'Päeval {day} pole kella {from} ja {to} vahel midagi plaanis. Mis sinna sobiks?',
|
||||
'assistant.hint.longLeg': '{from} ja {to} on linnulennult {distance} vahega',
|
||||
'assistant.hint.longLeg.prompt':
|
||||
'Kas päeval {day} on {from} ja {to} vahe liiga pikk? Kuidas sinna kõige paremini saab?',
|
||||
'assistant.hint.tightLeg': '{from} ja {to} vahel on ainult {count} minutit',
|
||||
'assistant.hint.tightLeg.one': '{from} ja {to} vahel on ainult {count} minut',
|
||||
'assistant.hint.tightLeg.prompt': 'Kas päeval {day} jõuab aegsasti {from} juurest {to} juurde? Mida peaksime muutma?',
|
||||
'assistant.hint.noStay': 'Alates päevast {day} pole veel majutust {count} ööks',
|
||||
'assistant.hint.noStay.one': 'Alates päevast {day} pole veel majutust {count} ööks',
|
||||
'assistant.hint.noStay.prompt': 'Alates päevast {day} on meil majutus veel puudu. Mida sa soovitad?',
|
||||
'assistant.hint.unplacedBooking': '{title} pole veel ühelgi päeval',
|
||||
'assistant.hint.unplacedBookingDay': '{title} kuulub ilmselt päevale {day}',
|
||||
'assistant.hint.unplacedBookings': '{count} broneeringut pole veel ühelgi päeval',
|
||||
'assistant.hint.unplacedBookings.one': '{count} broneering pole veel ühelgi päeval',
|
||||
'assistant.hint.unplacedBooking.prompt': 'Millised broneeringud pole veel ühelgi päeval ja kuhu need kuuluvad?',
|
||||
'assistant.hint.menuOn': 'Näita vihjeid',
|
||||
'assistant.hint.menuOff': 'Ära näita vihjeid',
|
||||
'assistant.launcher.withHints': 'Assistent, {count} vihjet',
|
||||
'assistant.launcher.withHints.one': 'Assistent, {count} vihje',
|
||||
'assistant.mobile.tileHints': 'Sinu plaanis on {count} asja üle vaadata',
|
||||
'assistant.mobile.tileHints.one': 'Sinu plaanis on {count} asi üle vaadata',
|
||||
'assistant.share.button': 'Jaga reisivestluses',
|
||||
'assistant.share.title': 'Jaga reisivestluses',
|
||||
'assistant.share.who': 'Kõik reisil osalejad näevad seda sõnumit, märgitud assistendi omaks.',
|
||||
'assistant.share.includeChanges': 'Lisa rakendatud muudatused',
|
||||
'assistant.share.changesHeading': 'Rakendatud:',
|
||||
'assistant.share.truncated': 'Lühendatud {count} märgini',
|
||||
'assistant.share.truncated.one': 'Lühendatud {count} märgini',
|
||||
'assistant.share.send': 'Jaga',
|
||||
'assistant.share.done': 'Jagatud reisivestluses',
|
||||
'assistant.share.shared': 'Jagatud',
|
||||
'assistant.share.failed': 'Jagamine ei õnnestunud. Proovi hetke pärast uuesti.',
|
||||
'assistant.share.chatOff': 'Reisivestlus on välja lülitatud.',
|
||||
};
|
||||
export default assistant;
|
||||
@@ -106,5 +106,7 @@ const collab: TranslationStrings = {
|
||||
'collab.chat.attachImages': 'Lisa pildid',
|
||||
'collab.chat.attachedImage': 'Lisatud pilt',
|
||||
'collab.chat.closeImage': 'Sulge pilt',
|
||||
'collab.chat.fromAssistant': 'assistendilt',
|
||||
'collab.chat.fromAssistantHint': '{name} jagas seda assistendi vastust',
|
||||
};
|
||||
export default collab;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import admin from './admin';
|
||||
import airport from './airport';
|
||||
import assistant from './assistant';
|
||||
import atlas from './atlas';
|
||||
import backup from './backup';
|
||||
import budget from './budget';
|
||||
@@ -116,5 +117,6 @@ const locale = {
|
||||
...dawarich,
|
||||
...docsync,
|
||||
...ferratas,
|
||||
...assistant,
|
||||
};
|
||||
export default locale;
|
||||
|
||||
@@ -85,6 +85,7 @@ const oauth: TranslationStrings = {
|
||||
'oauth.authorize.loginButton': 'Logi TREKi sisse',
|
||||
'oauth.authorize.requestLabel': 'Juurdepääsutaotlus',
|
||||
'oauth.authorize.requestDescription': 'See rakendus taotleb juurdepääsu sinu TREKi kontole.',
|
||||
'oauth.authorize.verifiedHost': 'Kinnitatud domeen: {host}',
|
||||
'oauth.authorize.trustNote': 'Anna juurdepääs ainult usaldatud rakendustele. Sinu andmed jäävad sinu serverisse.',
|
||||
'oauth.authorize.selectScope': 'Vali vähemalt üks õiguste ulatus',
|
||||
'oauth.authorize.approveScopes': 'Luba ({count} ulatust)',
|
||||
|
||||
@@ -647,6 +647,9 @@ const admin: TranslationStrings = {
|
||||
'admin.addons.catalog.llm_parsing.name': 'Analyse par IA',
|
||||
'admin.addons.catalog.llm_parsing.description':
|
||||
'Lit les réservations que l’analyseur intégré ne comprend pas, via un modèle d’IA de votre choix',
|
||||
'admin.addons.catalog.ai_assistant.name': 'Assistant IA',
|
||||
'admin.addons.catalog.ai_assistant.description':
|
||||
'Un chat dans chaque voyage qui répond aux questions et propose des modifications que vous confirmez',
|
||||
'admin.addons.llm.vision.auto': 'Automatique',
|
||||
'admin.addons.llm.vision.on': 'Oui',
|
||||
'admin.addons.llm.vision.off': 'Non',
|
||||
|
||||
@@ -0,0 +1,346 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistant: TranslationStrings = {
|
||||
'assistant.launcher.tooltip': 'Assistant', // same-as-en
|
||||
'assistant.launcher.ctrlKey': 'Ctrl', // same-as-en
|
||||
'assistant.picker.title': 'Quel voyage ?',
|
||||
'assistant.picker.empty': 'Créez d’abord un voyage, l’assistant pourra ensuite vous aider à le planifier.',
|
||||
'assistant.picker.now': 'En cours',
|
||||
'assistant.picker.next': 'À venir',
|
||||
'assistant.window.title': 'Assistant', // same-as-en
|
||||
'assistant.window.log': 'Conversation avec l’assistant',
|
||||
'assistant.window.menu': 'Plus d’options',
|
||||
'assistant.window.settings': 'Paramètres IA',
|
||||
'assistant.window.deleteThread': 'Supprimer le chat',
|
||||
'assistant.window.deleteConfirm': 'Supprimer ce chat ? Cette action est irréversible.',
|
||||
'assistant.setup.question': 'Avec quoi souhaitez-vous discuter ?',
|
||||
'assistant.setup.beta': 'Bêta',
|
||||
'assistant.setup.chatgptNote': 'Une clé API est la voie officielle. La connexion avec ChatGPT est encore en bêta.',
|
||||
'assistant.setup.keySubtitle': 'Anthropic, OpenAI, Gemini ou OpenRouter',
|
||||
'assistant.setup.adminOnly': 'C’est votre administrateur qui le configure.',
|
||||
'assistant.setup.forEveryone': 'Configurer pour tout le monde',
|
||||
'assistant.connect.title': 'Assistant IA',
|
||||
'assistant.connect.description':
|
||||
'Discutez avec un assistant qui connaît votre voyage et propose des modifications que vous confirmez.',
|
||||
'assistant.connect.chatgpt': 'Se connecter avec ChatGPT',
|
||||
'assistant.connect.chatgptHint': 'Utilise votre abonnement ChatGPT. Aucune clé nécessaire.',
|
||||
'assistant.connect.chatgptStep1': 'Connectez-vous à ChatGPT dans le nouvel onglet.',
|
||||
'assistant.connect.chatgptOpen': 'Ouvrir la connexion ChatGPT',
|
||||
'assistant.connect.chatgptStep2':
|
||||
'Votre navigateur affichera ensuite une page d’erreur. C’est normal. Copiez l’adresse tout en haut et revenez ici.',
|
||||
'assistant.connect.chatgptPaste': 'Coller l’adresse',
|
||||
'assistant.connect.chatgptWrong': 'Ce n’est pas la bonne adresse. Elle commence par 127.0.0.1.',
|
||||
'assistant.connect.chatgptExpired': 'Cela a pris trop de temps. Veuillez vous reconnecter.',
|
||||
'assistant.connect.chatgptOtherAccount': 'C’est un autre compte ChatGPT. Supprimez d’abord la connexion.',
|
||||
'assistant.connect.key': 'Coller une clé API',
|
||||
'assistant.connect.keyPlaceholder': 'Collez votre clé ici',
|
||||
'assistant.connect.detected': 'Détecté : {provider}',
|
||||
'assistant.connect.whichProvider': 'De quel fournisseur vient cette clé ?',
|
||||
'assistant.connect.testing': 'Vérification de la connexion',
|
||||
'assistant.connect.ok': 'Connecté. C’est parti.',
|
||||
'assistant.connect.openAssistant': 'Ouvrir l’assistant',
|
||||
'assistant.connect.badKey': 'Cette clé ne fonctionne pas.',
|
||||
'assistant.connect.unknownModel': 'Ce modèle n’est pas proposé ici.',
|
||||
'assistant.connect.getKey': 'Obtenir une clé',
|
||||
'assistant.connect.hintAnthropic': 'Claude Max et Team incluent des crédits API.',
|
||||
'assistant.connect.hintOpenai': 'Créez une clé dans votre tableau de bord OpenAI.',
|
||||
'assistant.connect.hintGemini': 'AI Studio propose une offre gratuite avec des limites par minute et par jour, sur laquelle Google peut utiliser ce que tu envoies pour améliorer ses produits. Avec la facturation activée, les limites sont plus élevées et cela ne s’applique pas.',
|
||||
'assistant.connect.hintOpenrouter': 'Connectez-vous avec OpenRouter, aucune clé à copier.',
|
||||
'assistant.connect.openrouter': 'Connecter OpenRouter',
|
||||
'assistant.connect.save': 'Connecter',
|
||||
'assistant.connect.model': 'Modèle',
|
||||
'assistant.connect.modelHint': 'Seuls les modèles capables d’utiliser des outils sont listés.',
|
||||
'assistant.connect.free': 'Offre gratuite disponible',
|
||||
'assistant.connect.paid': 'Paiement à l’usage',
|
||||
'assistant.model.recommended': 'Recommandé',
|
||||
'assistant.model.free': 'Gratuit',
|
||||
'assistant.connect.connected': 'Connecté à {provider}',
|
||||
'assistant.connect.providedByAdmin': 'Fourni par votre administrateur',
|
||||
'assistant.connect.disconnect': 'Déconnecter',
|
||||
'assistant.connect.disconnectConfirm': 'Déconnecter {provider} ? Vos chats sont conservés.',
|
||||
'assistant.connect.signOut': 'Se déconnecter',
|
||||
'assistant.connect.statusOk': 'Connecté',
|
||||
'assistant.connect.statusNeedsReauth': 'Connexion expirée',
|
||||
'assistant.connect.statusSignedOut': 'Déconnecté',
|
||||
'assistant.connect.statusError': 'Injoignable',
|
||||
'assistant.connect.quota': '{used} messages sur {count} utilisés aujourd’hui',
|
||||
'assistant.connect.quota.one': '{used} message sur {count} utilisé aujourd’hui',
|
||||
'assistant.connect.claudeLink': 'Vous utilisez Claude ? Connectez-vous dans Claude',
|
||||
'assistant.privacy.what': 'Qu’est-ce qui est partagé ?',
|
||||
'assistant.privacy.banner': 'Vos messages et les données du voyage que lit l’assistant sont envoyés à {provider}.',
|
||||
'assistant.privacy.ok': 'Compris',
|
||||
'assistant.privacy.shared':
|
||||
'Vos messages et les détails du voyage que lit l’assistant, comme les lieux, les jours et les réservations.',
|
||||
'assistant.privacy.notShared': 'Ni votre mot de passe, ni vos fichiers, ni vos autres voyages.',
|
||||
'assistant.privacy.provider': '{provider} traite ces données selon ses propres règles de confidentialité.',
|
||||
'assistant.empty.title': 'Qu’est-ce qu’on planifie ?',
|
||||
'assistant.empty.readOnly':
|
||||
'Vous pouvez seulement consulter ce voyage : je peux répondre aux questions, sans rien modifier.',
|
||||
'assistant.suggest.planFirstDays': 'Planifie les premiers jours pour moi',
|
||||
'assistant.suggest.mustSee': 'Qu’est-ce qu’il ne faut pas manquer ici ?',
|
||||
'assistant.suggest.packingList': 'Fais une liste de bagages pour ce voyage',
|
||||
'assistant.suggest.emptyDays': 'Quels jours sont encore vides ?',
|
||||
'assistant.suggest.rainPlan': 'Que faire s’il pleut ?',
|
||||
'assistant.suggest.today': 'Qu’est-ce qui est prévu aujourd’hui ?',
|
||||
'assistant.suggest.eatNearby': 'Trouve un endroit où manger à proximité',
|
||||
'assistant.suggest.weather': 'Quel temps va-t-il faire ?',
|
||||
'assistant.suggest.openTodos': 'Qu’est-ce qui reste à faire dans la liste ?',
|
||||
'assistant.suggest.settleUp': 'Qui doit combien à qui ?',
|
||||
'assistant.suggest.recap': 'Résume notre voyage',
|
||||
'assistant.suggest.nearPlace': 'Qu’y a-t-il d’autre près de {place} ?',
|
||||
'assistant.suggest.placeTips': 'Conseils pour visiter {place}',
|
||||
'assistant.suggest.planDay': 'Planifie {day} pour moi',
|
||||
'assistant.suggest.dayTooFull': '{day} est-il trop chargé ?',
|
||||
'assistant.thread.scrollDown': 'Aller au dernier message',
|
||||
'assistant.attach.button': 'Joindre une réservation',
|
||||
'assistant.attach.drop': 'Dépose-le ici et je lis la réservation',
|
||||
'assistant.attach.unsupported': 'Je ne peux pas lire ce type de fichier. Essaie un PDF, un e-mail, une image ou un pass Wallet.',
|
||||
'assistant.attach.tooLarge': 'Ce fichier dépasse 10 Mo.',
|
||||
'assistant.composer.placeholder': 'Posez une question sur {trip}',
|
||||
'assistant.composer.send': 'Envoyer',
|
||||
'assistant.composer.stop': 'Arrêter',
|
||||
'assistant.composer.decideFirst': 'Appliquez ou ignorez d’abord les modifications ci-dessus.',
|
||||
'assistant.context.ask': 'Demander',
|
||||
'assistant.context.remove': 'Retirer le contexte',
|
||||
'assistant.step.reading': 'Lecture de votre plan',
|
||||
'assistant.step.searching': 'Recherche de lieux',
|
||||
'assistant.step.details': 'Recherche des détails',
|
||||
'assistant.step.nearby': 'Recherche à proximité',
|
||||
'assistant.step.categories': 'Vérification des catégories',
|
||||
'assistant.step.weather': 'Consultation de la météo',
|
||||
'assistant.step.todos': 'Lecture de la liste de tâches',
|
||||
'assistant.step.costs': 'Vérification des dépenses',
|
||||
'assistant.step.help': 'Recherche dans l’aide',
|
||||
'assistant.step.readingBooking': 'Lecture de la réservation',
|
||||
'assistant.step.working': 'Travail en cours',
|
||||
'assistant.step.collapsed': '{count} étapes',
|
||||
'assistant.step.collapsed.one': '{count} étape',
|
||||
'assistant.change.title': 'Modifications proposées',
|
||||
'assistant.change.doneTitle': 'Terminé',
|
||||
'assistant.change.apply': 'Appliquer',
|
||||
'assistant.change.applyN': 'Appliquer {count}',
|
||||
'assistant.change.applyN.one': 'Appliquer {count}',
|
||||
'assistant.change.discard': 'Ignorer',
|
||||
'assistant.change.include': 'Inclure cette modification',
|
||||
'assistant.change.applied': 'Appliqué',
|
||||
'assistant.change.discarded': 'Ignoré',
|
||||
'assistant.change.failed': 'Impossible d’appliquer ceci.',
|
||||
'assistant.change.applying': 'Application en cours',
|
||||
'assistant.change.expired': 'Plus disponible',
|
||||
'assistant.change.undo': 'Annuler',
|
||||
'assistant.change.undone': 'Annulé',
|
||||
'assistant.change.undoStale': 'Modifié entre-temps. Veuillez l’ajuster à la main.',
|
||||
'assistant.change.undoFailed': 'Impossible d’annuler ceci.',
|
||||
'assistant.change.showOnMap': 'Afficher sur la carte',
|
||||
'assistant.change.empty': 'vide',
|
||||
'assistant.change.pending': '{count} modifications vous attendent',
|
||||
'assistant.change.pending.one': '{count} modification vous attend',
|
||||
'assistant.change.verb.addPlace': 'Ajouter un lieu',
|
||||
'assistant.change.verb.addPlaceToDay': 'Ajouter un lieu au jour',
|
||||
'assistant.change.verb.updatePlace': 'Modifier le lieu',
|
||||
'assistant.change.verb.assignPlace': 'Planifier le lieu un jour',
|
||||
'assistant.change.verb.moveAssignment': 'Déplacer vers un autre jour',
|
||||
'assistant.change.verb.removeFromDay': 'Retirer de la journée',
|
||||
'assistant.change.verb.addDayNote': 'Ajouter une note',
|
||||
'assistant.change.verb.updateDayNote': 'Modifier la note',
|
||||
'assistant.change.verb.addPackingItem': 'Ajouter à la liste de bagages',
|
||||
'assistant.change.verb.updatePackingItem': 'Modifier l’article à emporter',
|
||||
'assistant.change.verb.addTodo': 'Ajouter une tâche',
|
||||
'assistant.change.verb.addBudgetItem': 'Ajouter une dépense',
|
||||
'assistant.change.verb.addReservation': 'Ajouter une réservation',
|
||||
'assistant.change.verb.importBooking': 'Ajouter une réservation depuis un fichier',
|
||||
'assistant.change.label.name': 'Nom',
|
||||
'assistant.change.label.description': 'Description', // same-as-en
|
||||
'assistant.change.label.address': 'Adresse',
|
||||
'assistant.change.label.coordinates': 'Position sur la carte',
|
||||
'assistant.change.label.category': 'Catégorie',
|
||||
'assistant.change.label.day': 'Jour',
|
||||
'assistant.change.label.time': 'Heure',
|
||||
'assistant.change.label.endTime': 'Jusqu’à',
|
||||
'assistant.change.label.notes': 'Notes', // same-as-en
|
||||
'assistant.change.label.website': 'Site web',
|
||||
'assistant.change.label.phone': 'Téléphone',
|
||||
'assistant.change.label.email': 'E-mail',
|
||||
'assistant.change.label.openingHours': 'Horaires d’ouverture',
|
||||
'assistant.change.label.price': 'Prix',
|
||||
'assistant.change.label.currency': 'Devise',
|
||||
'assistant.change.label.stopType': 'Type d’arrêt',
|
||||
'assistant.change.label.text': 'Texte',
|
||||
'assistant.change.label.icon': 'Icône',
|
||||
'assistant.change.label.color': 'Couleur',
|
||||
'assistant.change.label.position': 'Ordre',
|
||||
'assistant.change.label.quantity': 'Quantité',
|
||||
'assistant.change.label.weight': 'Poids',
|
||||
'assistant.change.label.bag': 'Sac',
|
||||
'assistant.change.label.packed': 'Emballé',
|
||||
'assistant.change.label.dueDate': 'Échéance',
|
||||
'assistant.change.label.priority': 'Priorité',
|
||||
'assistant.change.label.assignee': 'Attribué à',
|
||||
'assistant.change.label.type': 'Type', // same-as-en
|
||||
'assistant.change.label.location': 'Lieu',
|
||||
'assistant.change.label.link': 'Lien',
|
||||
'assistant.change.label.confirmation': 'Numéro de confirmation',
|
||||
'assistant.change.label.checkIn': 'Arrivée',
|
||||
'assistant.change.label.checkOut': 'Départ',
|
||||
'assistant.change.label.stay': 'Séjour',
|
||||
'assistant.change.label.members': 'Réparti entre',
|
||||
'assistant.change.label.route': 'Trajet',
|
||||
'assistant.history.open': 'Chats récents',
|
||||
'assistant.history.new': 'Nouveau chat',
|
||||
'assistant.history.empty': 'Aucun chat pour l’instant.',
|
||||
'assistant.history.retention': 'Les chats sont supprimés après {count} jours.',
|
||||
'assistant.history.retention.one': 'Les chats sont supprimés après {count} jour.',
|
||||
'assistant.history.forever': 'Les chats sont conservés jusqu’à ce que vous les supprimiez.',
|
||||
'assistant.error.notConnected': 'L’assistant n’est pas encore connecté.',
|
||||
'assistant.error.busy': 'Déjà en cours dans une autre fenêtre.',
|
||||
'assistant.error.dailyLimit': 'C’est tout pour aujourd’hui. À demain.',
|
||||
'assistant.error.rateLimited': 'Doucement. Réessayez dans une minute.',
|
||||
'assistant.error.needsReauth': 'Votre connexion à {provider} a expiré.',
|
||||
'assistant.error.usageLimit': 'Votre quota ChatGPT est épuisé.',
|
||||
'assistant.error.notEligible': 'Ce compte ChatGPT ne peut pas être utilisé ici.',
|
||||
'assistant.error.invalidKey': 'Votre clé {provider} ne fonctionne plus.',
|
||||
'assistant.error.providerDown': '{provider} ne répond pas pour le moment.',
|
||||
'assistant.error.maxSteps': 'Ça faisait beaucoup d’un coup. Je continue ?',
|
||||
'assistant.error.offline': 'Hors ligne. L’assistant a besoin d’Internet.',
|
||||
'assistant.error.noBookingsFound': 'Aucune réservation trouvée dans ce fichier.',
|
||||
'assistant.error.importUnavailable': 'La lecture des fichiers de réservation n’est pas disponible ici.',
|
||||
'assistant.error.generic': 'Une erreur s’est produite.',
|
||||
'assistant.errorAction.retry': 'Réessayer',
|
||||
'assistant.errorAction.continueHere': 'Continuer ici',
|
||||
'assistant.errorAction.continue': 'Continuer',
|
||||
'assistant.errorAction.signIn': 'Se reconnecter',
|
||||
'assistant.errorAction.viewUsage': 'Voir l’utilisation',
|
||||
'assistant.errorAction.otherProvider': 'Choisir un autre fournisseur',
|
||||
'assistant.errorAction.replaceKey': 'Remplacer la clé',
|
||||
'assistant.errorAction.connectOwn': 'Connecter votre propre fournisseur',
|
||||
'assistant.admin.policy': 'Qui paie',
|
||||
'assistant.admin.policy.instance': 'Je paie pour tout le monde',
|
||||
'assistant.admin.policy.instanceOrUser': 'Je paie, compte perso autorisé',
|
||||
'assistant.admin.policy.user': 'Chacun utilise son propre compte',
|
||||
'assistant.admin.policy.instanceHint':
|
||||
'Tout le monde utilise la connexion ci-dessous et ne peut pas ajouter la sienne.',
|
||||
'assistant.admin.policy.instanceOrUserHint':
|
||||
'Tout le monde utilise la connexion ci-dessous, sauf s’il connecte la sienne.',
|
||||
'assistant.admin.policy.userHint': 'Chacun connecte son propre fournisseur. Rien ne vous est facturé.',
|
||||
'assistant.admin.connection': 'Connexion pour tout le monde',
|
||||
'assistant.admin.connectionNone': 'Aucune connexion pour l’instant.',
|
||||
'assistant.admin.connectionRemove': 'Supprimer la connexion',
|
||||
'assistant.admin.custom': 'Serveur personnel',
|
||||
'assistant.admin.customHint': 'N’importe quel serveur compatible OpenAI, par exemple Ollama.',
|
||||
'assistant.admin.baseUrl': 'Adresse du serveur',
|
||||
'assistant.admin.apiKeyOptional': 'Clé API (facultative)',
|
||||
'assistant.admin.prefilled': 'Repris depuis Analyse par IA.',
|
||||
'assistant.admin.dailyLimit': 'Messages par personne et par jour',
|
||||
'assistant.admin.dailyLimitHint': 'Compte uniquement tant que vous payez.',
|
||||
'assistant.admin.retention': 'Conserver les chats',
|
||||
'assistant.admin.retentionDays': '{count} jours',
|
||||
'assistant.admin.retentionDays.one': '{count} jour',
|
||||
'assistant.admin.retentionForever': 'Pour toujours',
|
||||
'assistant.claude.title': 'Se connecter avec Claude',
|
||||
'assistant.claude.description':
|
||||
'Utilisez TREK directement dans Claude : ajoutez-le comme connecteur, connectez-vous, c’est fait.',
|
||||
'assistant.claude.open': 'Ouvrir dans Claude',
|
||||
'assistant.claude.scan': 'Ou scannez-le avec votre téléphone',
|
||||
'assistant.claude.copyUrl': 'Copier l’adresse',
|
||||
'assistant.claude.qrAlt': 'QR code pour ajouter TREK dans Claude',
|
||||
'assistant.claude.notPublic': 'Claude ne peut joindre TREK que s’il est accessible en https sur Internet.',
|
||||
'assistant.mobile.tile': 'Demander à TREK',
|
||||
'assistant.mobile.tileHint': 'Planifier avec l’assistant',
|
||||
'assistant.mobile.backToChat': 'Retour au chat',
|
||||
'assistant.step.nearbyAt': 'Recherche près de {place}',
|
||||
'assistant.step.searchingFor': 'Recherche de {query}',
|
||||
'assistant.step.weatherAt': 'Vérifie la météo à {place}',
|
||||
'assistant.change.timeline.show': 'Afficher le déroulé',
|
||||
'assistant.change.timeline.hide': 'Masquer le déroulé',
|
||||
'assistant.change.timeline.new': 'Nouveau',
|
||||
'assistant.change.timeline.travel': '≈ {count} min de trajet',
|
||||
'assistant.change.timeline.travel.one': '≈ {count} min de trajet',
|
||||
'assistant.change.timeline.gap': '{count} min libres',
|
||||
'assistant.change.timeline.gap.one': '{count} min libre',
|
||||
'assistant.change.timeline.overlap': 'Chevauche {place}',
|
||||
'assistant.change.timeline.moreDays': '{count} autres jours',
|
||||
'assistant.change.timeline.moreDays.one': '{count} autre jour',
|
||||
'assistant.change.viewOnMap': 'Voir sur la carte',
|
||||
'assistant.change.before': 'Avant',
|
||||
'assistant.change.after': 'Après',
|
||||
'assistant.change.compare': 'Comparer sur la carte',
|
||||
'assistant.change.undoAll': 'Tout annuler',
|
||||
'assistant.change.undoneAll': '{count} modifications annulées',
|
||||
'assistant.change.undoneAll.one': '{count} modification annulée',
|
||||
'assistant.change.undoAllPartial': 'Annulées : {done}, modifiées entre-temps : {stale}',
|
||||
'assistant.change.undoAllFailed': '{count} modifications n’ont pas pu être annulées',
|
||||
'assistant.change.undoAllFailed.one': '{count} modification n’a pas pu être annulée',
|
||||
'assistant.place.strip': 'Lieux trouvés',
|
||||
'assistant.place.addTo': 'Ajouter à {day}',
|
||||
'assistant.place.addToShort': '+ Jour {n}',
|
||||
'assistant.place.pickDay': 'Choisir un jour',
|
||||
'assistant.place.suggested': 'Proposé',
|
||||
'assistant.place.inTrip': 'Déjà dans votre voyage',
|
||||
'assistant.place.showOnMap': 'Sur la carte',
|
||||
'assistant.place.openNow': 'Ouvert',
|
||||
'assistant.place.closed': 'Fermé',
|
||||
'assistant.place.fromPrev': '{distance} de l’étape précédente',
|
||||
'assistant.place.more': '{count} de plus',
|
||||
'assistant.place.more.one': '{count} de plus',
|
||||
'assistant.mention.days': 'Jours',
|
||||
'assistant.mention.places': 'Lieux',
|
||||
'assistant.mention.bookings': 'Réservations',
|
||||
'assistant.mention.none': 'Aucun résultat',
|
||||
'assistant.mention.hint': 'Tapez @ pour citer des jours, des lieux et des réservations.',
|
||||
'assistant.attach.dropPlace': 'Déposez le lieu ici',
|
||||
'assistant.weather.strip': 'Météo sur {count} jours',
|
||||
'assistant.weather.strip.one': 'Météo sur {count} jour',
|
||||
'assistant.weather.rain': '{pct} % de pluie',
|
||||
'assistant.settings.followMap': 'La carte suit l’assistant',
|
||||
'assistant.settings.followMapHint': 'La carte se déplace là où l’assistant cherche et propose des lieux.',
|
||||
'assistant.error.decideFirst': 'Décidez d’abord des propositions en attente.',
|
||||
'assistant.error.providerLimit': 'Limite atteinte chez {provider}. Réessayez plus tard.',
|
||||
'assistant.hint.title': 'À vérifier',
|
||||
'assistant.hint.dismiss': 'Masquer cette remarque',
|
||||
'assistant.hint.more': '{count} autres',
|
||||
'assistant.hint.more.one': '{count} autre',
|
||||
'assistant.hint.overlap': '{a} et {b} se chevauchent le {day}',
|
||||
'assistant.hint.overlap.prompt': 'Le {day}, {a} et {b} se chevauchent. Comment arranger ça ?',
|
||||
'assistant.hint.closed': '{place} est fermé le {day}',
|
||||
'assistant.hint.closed.prompt': '{place} est fermé le {day}. Que faire à la place ?',
|
||||
'assistant.hint.opensLate': '{place} n’ouvre qu’à {time} le {day}',
|
||||
'assistant.hint.closesEarly': '{place} ferme dès {time} le {day}',
|
||||
'assistant.hint.outsideHours.prompt':
|
||||
'Notre visite de {place} le {day} ne colle pas aux horaires d’ouverture. Comment la déplacer au mieux ?',
|
||||
'assistant.hint.gap': '{day} : {count} heures libres à partir de {from}',
|
||||
'assistant.hint.gap.one': '{day} : {count} heure libre à partir de {from}',
|
||||
'assistant.hint.gap.prompt': 'Rien n’est prévu le {day} de {from} à {to}. Que pourrait-on y caser ?',
|
||||
'assistant.hint.longLeg': '{from} et {to} sont à {distance} à vol d’oiseau',
|
||||
'assistant.hint.longLeg.prompt': 'Le trajet de {from} à {to} le {day} est-il trop long ? Comment y aller au mieux ?',
|
||||
'assistant.hint.tightLeg': 'Seulement {count} minutes entre {from} et {to}',
|
||||
'assistant.hint.tightLeg.one': 'Seulement {count} minute entre {from} et {to}',
|
||||
'assistant.hint.tightLeg.prompt':
|
||||
'A-t-on assez de temps le {day} pour aller de {from} à {to} ? Que faut-il changer ?',
|
||||
'assistant.hint.noStay': 'Pas encore d’hébergement pour {count} nuits à partir du {day}',
|
||||
'assistant.hint.noStay.one': 'Pas encore d’hébergement pour {count} nuit à partir du {day}',
|
||||
'assistant.hint.noStay.prompt': 'Il nous manque un hébergement à partir du {day}. Que proposez-vous ?',
|
||||
'assistant.hint.unplacedBooking': '{title} n’est rattaché à aucun jour',
|
||||
'assistant.hint.unplacedBookingDay': '{title} va sans doute avec le {day}',
|
||||
'assistant.hint.unplacedBookings': '{count} réservations ne sont rattachées à aucun jour',
|
||||
'assistant.hint.unplacedBookings.one': '{count} réservation n’est rattachée à aucun jour',
|
||||
'assistant.hint.unplacedBooking.prompt': 'Quelles réservations ne sont rattachées à aucun jour, et où vont-elles ?',
|
||||
'assistant.hint.menuOn': 'Afficher les remarques',
|
||||
'assistant.hint.menuOff': 'Ne plus afficher les remarques',
|
||||
'assistant.launcher.withHints': 'Assistant, {count} remarques',
|
||||
'assistant.launcher.withHints.one': 'Assistant, {count} remarque',
|
||||
'assistant.mobile.tileHints': '{count} points à vérifier dans votre plan',
|
||||
'assistant.mobile.tileHints.one': '{count} point à vérifier dans votre plan',
|
||||
'assistant.share.button': 'Partager dans le chat du voyage',
|
||||
'assistant.share.title': 'Partager dans le chat du voyage',
|
||||
'assistant.share.who': 'Tous les participants verront ce message, signalé comme venant de l’assistant.',
|
||||
'assistant.share.includeChanges': 'Joindre les modifications appliquées',
|
||||
'assistant.share.changesHeading': 'Appliqué :',
|
||||
'assistant.share.truncated': 'Raccourci à {count} caractères',
|
||||
'assistant.share.truncated.one': 'Raccourci à {count} caractère',
|
||||
'assistant.share.send': 'Partager',
|
||||
'assistant.share.done': 'Partagé dans le chat du voyage',
|
||||
'assistant.share.shared': 'Partagé',
|
||||
'assistant.share.failed': 'Le partage a échoué. Réessayez dans un instant.',
|
||||
'assistant.share.chatOff': 'Le chat du voyage est désactivé.',
|
||||
};
|
||||
export default assistant;
|
||||
@@ -106,5 +106,7 @@ const collab: TranslationStrings = {
|
||||
'collab.chat.attachImages': 'Joindre des images',
|
||||
'collab.chat.attachedImage': 'Image jointe',
|
||||
'collab.chat.closeImage': 'Fermer l’image',
|
||||
'collab.chat.fromAssistant': 'de l’assistant',
|
||||
'collab.chat.fromAssistantHint': '{name} a partagé cette réponse de l’assistant',
|
||||
};
|
||||
export default collab;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import admin from './admin';
|
||||
import airport from './airport';
|
||||
import assistant from './assistant';
|
||||
import atlas from './atlas';
|
||||
import backup from './backup';
|
||||
import budget from './budget';
|
||||
@@ -116,5 +117,6 @@ const locale = {
|
||||
...dawarich,
|
||||
...docsync,
|
||||
...ferratas,
|
||||
...assistant,
|
||||
};
|
||||
export default locale;
|
||||
|
||||
@@ -86,6 +86,7 @@ const oauth: TranslationStrings = {
|
||||
'oauth.authorize.loginButton': 'Sign in to TREK', // en-fallback
|
||||
'oauth.authorize.requestLabel': 'Authorization Request', // en-fallback
|
||||
'oauth.authorize.requestDescription': 'This application is requesting access to your TREK account.', // en-fallback
|
||||
'oauth.authorize.verifiedHost': 'Domaine vérifié : {host}',
|
||||
'oauth.authorize.trustNote': 'Only grant access to applications you trust. Your data stays on your server.', // en-fallback
|
||||
'oauth.authorize.selectScope': 'Select at least one scope', // en-fallback
|
||||
'oauth.authorize.approveScopes': 'Approuver ({count} portées)',
|
||||
|
||||
@@ -709,6 +709,9 @@ const admin: TranslationStrings = {
|
||||
'admin.addons.catalog.llm_parsing.name': 'Ανάλυση με τεχνητή νοημοσύνη',
|
||||
'admin.addons.catalog.llm_parsing.description':
|
||||
'Διαβάζει κρατήσεις που δεν καταλαβαίνει ο ενσωματωμένος αναλυτής, με μοντέλο ΤΝ της επιλογής σας',
|
||||
'admin.addons.catalog.ai_assistant.name': 'Βοηθός τεχνητής νοημοσύνης',
|
||||
'admin.addons.catalog.ai_assistant.description':
|
||||
'Συνομιλία σε κάθε ταξίδι που απαντά σε ερωτήσεις και προτείνει αλλαγές που εσύ επιβεβαιώνεις',
|
||||
'admin.addons.llm.vision.auto': 'Αυτόματα',
|
||||
'admin.addons.llm.vision.on': 'Ναι',
|
||||
'admin.addons.llm.vision.off': 'Όχι',
|
||||
|
||||
@@ -0,0 +1,346 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistant: TranslationStrings = {
|
||||
'assistant.launcher.tooltip': 'Βοηθός',
|
||||
'assistant.launcher.ctrlKey': 'Ctrl',
|
||||
'assistant.picker.title': 'Ποιο ταξίδι;',
|
||||
'assistant.picker.empty': 'Δημιουργήστε πρώτα ένα ταξίδι και ο βοηθός θα σας βοηθήσει να το σχεδιάσετε.',
|
||||
'assistant.picker.now': 'Τώρα',
|
||||
'assistant.picker.next': 'Επόμενο',
|
||||
'assistant.window.title': 'Βοηθός',
|
||||
'assistant.window.log': 'Συνομιλία με τον βοηθό',
|
||||
'assistant.window.menu': 'Περισσότερες επιλογές',
|
||||
'assistant.window.settings': 'Ρυθμίσεις τεχνητής νοημοσύνης',
|
||||
'assistant.window.deleteThread': 'Διαγραφή συνομιλίας',
|
||||
'assistant.window.deleteConfirm': 'Διαγραφή αυτής της συνομιλίας; Δεν αναιρείται.',
|
||||
'assistant.setup.question': 'Με τι θέλετε να συνομιλήσετε;',
|
||||
'assistant.setup.beta': 'Δοκιμαστικό',
|
||||
'assistant.setup.chatgptNote':
|
||||
'Το κλειδί API είναι ο επίσημος τρόπος. Η σύνδεση με ChatGPT είναι ακόμη σε δοκιμαστική φάση.',
|
||||
'assistant.setup.keySubtitle': 'Anthropic, OpenAI, Gemini ή OpenRouter',
|
||||
'assistant.setup.adminOnly': 'Αυτό το ρυθμίζει ο διαχειριστής σας.',
|
||||
'assistant.setup.forEveryone': 'Ρύθμιση για όλους',
|
||||
'assistant.connect.title': 'Βοηθός τεχνητής νοημοσύνης',
|
||||
'assistant.connect.description':
|
||||
'Συνομιλήστε με έναν βοηθό που γνωρίζει το ταξίδι σας και προτείνει αλλαγές που επιβεβαιώνετε εσείς.',
|
||||
'assistant.connect.chatgpt': 'Σύνδεση με ChatGPT',
|
||||
'assistant.connect.chatgptHint': 'Χρησιμοποιεί το πρόγραμμα ChatGPT σας. Χωρίς κλειδί.',
|
||||
'assistant.connect.chatgptStep1': 'Συνδεθείτε στο ChatGPT στη νέα καρτέλα.',
|
||||
'assistant.connect.chatgptOpen': 'Άνοιγμα σύνδεσης ChatGPT',
|
||||
'assistant.connect.chatgptStep2':
|
||||
'Το πρόγραμμα περιήγησης θα εμφανίσει μετά μια σελίδα σφάλματος. Αυτό είναι αναμενόμενο. Αντιγράψτε τη διεύθυνση από πάνω και επιστρέψτε εδώ.',
|
||||
'assistant.connect.chatgptPaste': 'Επικόλληση διεύθυνσης',
|
||||
'assistant.connect.chatgptWrong': 'Αυτή δεν είναι η σωστή διεύθυνση. Ξεκινά με 127.0.0.1.',
|
||||
'assistant.connect.chatgptExpired': 'Πήρε πολύ χρόνο. Συνδεθείτε ξανά.',
|
||||
'assistant.connect.chatgptOtherAccount': 'Αυτός είναι άλλος λογαριασμός ChatGPT. Αφαιρέστε πρώτα τη σύνδεση.',
|
||||
'assistant.connect.key': 'Επικόλληση κλειδιού API',
|
||||
'assistant.connect.keyPlaceholder': 'Επικολλήστε το κλειδί σας εδώ',
|
||||
'assistant.connect.detected': 'Εντοπίστηκε: {provider}',
|
||||
'assistant.connect.whichProvider': 'Από ποιον πάροχο είναι αυτό το κλειδί;',
|
||||
'assistant.connect.testing': 'Έλεγχος σύνδεσης',
|
||||
'assistant.connect.ok': 'Συνδέθηκε. Πάμε.',
|
||||
'assistant.connect.openAssistant': 'Άνοιγμα βοηθού',
|
||||
'assistant.connect.badKey': 'Αυτό το κλειδί δεν λειτουργεί.',
|
||||
'assistant.connect.unknownModel': 'Αυτό το μοντέλο δεν προσφέρεται εδώ.',
|
||||
'assistant.connect.getKey': 'Λήψη κλειδιού',
|
||||
'assistant.connect.hintAnthropic': 'Τα Claude Max και Team περιλαμβάνουν μονάδες API.',
|
||||
'assistant.connect.hintOpenai': 'Δημιουργήστε ένα κλειδί στον πίνακα ελέγχου του OpenAI.',
|
||||
'assistant.connect.hintGemini': 'Το AI Studio έχει δωρεάν επίπεδο με όρια ανά λεπτό και ανά ημέρα, όπου η Google μπορεί να χρησιμοποιεί ό,τι στέλνεις για να βελτιώσει τα προϊόντα της. Με ενεργή χρέωση τα όρια είναι υψηλότερα και αυτό δεν ισχύει.',
|
||||
'assistant.connect.hintOpenrouter': 'Συνδεθείτε με το OpenRouter, χωρίς κλειδί για αντιγραφή.',
|
||||
'assistant.connect.openrouter': 'Σύνδεση OpenRouter',
|
||||
'assistant.connect.save': 'Σύνδεση',
|
||||
'assistant.connect.model': 'Μοντέλο',
|
||||
'assistant.connect.modelHint': 'Εμφανίζονται μόνο μοντέλα που μπορούν να χρησιμοποιούν εργαλεία.',
|
||||
'assistant.connect.free': 'Διαθέσιμο δωρεάν επίπεδο',
|
||||
'assistant.connect.paid': 'Πληρωμή ανά χρήση',
|
||||
'assistant.model.recommended': 'Προτείνεται',
|
||||
'assistant.model.free': 'Δωρεάν',
|
||||
'assistant.connect.connected': 'Συνδέθηκε με {provider}',
|
||||
'assistant.connect.providedByAdmin': 'Παρέχεται από τον διαχειριστή σας',
|
||||
'assistant.connect.disconnect': 'Αποσύνδεση',
|
||||
'assistant.connect.disconnectConfirm': 'Αποσύνδεση από {provider}; Οι συνομιλίες σας παραμένουν.',
|
||||
'assistant.connect.signOut': 'Αποσύνδεση',
|
||||
'assistant.connect.statusOk': 'Συνδεδεμένο',
|
||||
'assistant.connect.statusNeedsReauth': 'Η σύνδεση έληξε',
|
||||
'assistant.connect.statusSignedOut': 'Αποσυνδεδεμένο',
|
||||
'assistant.connect.statusError': 'Μη προσβάσιμο',
|
||||
'assistant.connect.quota': 'Σήμερα χρησιμοποιήθηκαν {used} από {count} μηνύματα',
|
||||
'assistant.connect.quota.one': 'Σήμερα χρησιμοποιήθηκαν {used} από {count} μήνυμα',
|
||||
'assistant.connect.claudeLink': 'Χρησιμοποιείτε Claude; Συνδεθείτε στο Claude',
|
||||
'assistant.privacy.what': 'Τι κοινοποιείται;',
|
||||
'assistant.privacy.banner':
|
||||
'Τα μηνύματά σας και τα δεδομένα του ταξιδιού που διαβάζει ο βοηθός πηγαίνουν στο {provider}.',
|
||||
'assistant.privacy.ok': 'Εντάξει',
|
||||
'assistant.privacy.shared':
|
||||
'Τα μηνύματά σας και οι λεπτομέρειες του ταξιδιού που διαβάζει ο βοηθός, όπως μέρη, ημέρες και κρατήσεις.',
|
||||
'assistant.privacy.notShared': 'Όχι ο κωδικός σας, τα αρχεία σας ή τα άλλα ταξίδια σας.',
|
||||
'assistant.privacy.provider': 'Το {provider} τα χειρίζεται σύμφωνα με τους δικούς του όρους απορρήτου.',
|
||||
'assistant.empty.title': 'Τι σχεδιάζουμε;',
|
||||
'assistant.empty.readOnly':
|
||||
'Μπορείτε μόνο να δείτε αυτό το ταξίδι, οπότε απαντώ σε ερωτήσεις αλλά δεν αλλάζω τίποτα.',
|
||||
'assistant.suggest.planFirstDays': 'Σχεδίασέ μου τις πρώτες μέρες',
|
||||
'assistant.suggest.mustSee': 'Τι δεν πρέπει να χάσω εδώ;',
|
||||
'assistant.suggest.packingList': 'Φτιάξε μια λίστα αποσκευών για αυτό το ταξίδι',
|
||||
'assistant.suggest.emptyDays': 'Ποιες μέρες είναι ακόμη κενές;',
|
||||
'assistant.suggest.rainPlan': 'Τι μπορούμε να κάνουμε αν βρέξει;',
|
||||
'assistant.suggest.today': 'Τι έχουμε σήμερα;',
|
||||
'assistant.suggest.eatNearby': 'Βρες κάπου να φάμε εδώ κοντά',
|
||||
'assistant.suggest.weather': 'Πώς θα είναι ο καιρός;',
|
||||
'assistant.suggest.openTodos': 'Τι εκκρεμεί ακόμη στη λίστα εργασιών;',
|
||||
'assistant.suggest.settleUp': 'Ποιος χρωστάει σε ποιον;',
|
||||
'assistant.suggest.recap': 'Κάνε μια περίληψη του ταξιδιού μας',
|
||||
'assistant.suggest.nearPlace': 'Τι άλλο υπάρχει κοντά σε {place};',
|
||||
'assistant.suggest.placeTips': 'Συμβουλές για επίσκεψη σε {place}',
|
||||
'assistant.suggest.planDay': 'Σχεδίασέ μου την ημέρα {day}',
|
||||
'assistant.suggest.dayTooFull': 'Είναι η ημέρα {day} πολύ γεμάτη;',
|
||||
'assistant.thread.scrollDown': 'Μετάβαση στο τελευταίο μήνυμα',
|
||||
'assistant.attach.button': 'Επισύναψη κράτησης',
|
||||
'assistant.attach.drop': 'Άφησέ το εδώ και θα διαβάσω την κράτηση',
|
||||
'assistant.attach.unsupported': 'Δεν μπορώ να διαβάσω αυτόν τον τύπο αρχείου. Δοκίμασε PDF, email, εικόνα ή κάρτα πορτοφολιού.',
|
||||
'assistant.attach.tooLarge': 'Αυτό το αρχείο είναι μεγαλύτερο από 10 MB.',
|
||||
'assistant.composer.placeholder': 'Ρωτήστε οτιδήποτε για {trip}',
|
||||
'assistant.composer.send': 'Αποστολή',
|
||||
'assistant.composer.stop': 'Διακοπή',
|
||||
'assistant.composer.decideFirst': 'Εφαρμόστε ή απορρίψτε πρώτα τις παραπάνω αλλαγές.',
|
||||
'assistant.context.ask': 'Ερώτηση',
|
||||
'assistant.context.remove': 'Αφαίρεση πλαισίου',
|
||||
'assistant.step.reading': 'Διαβάζω το πλάνο σας',
|
||||
'assistant.step.searching': 'Αναζήτηση μερών',
|
||||
'assistant.step.details': 'Αναζήτηση λεπτομερειών',
|
||||
'assistant.step.nearby': 'Αναζήτηση κοντά',
|
||||
'assistant.step.categories': 'Έλεγχος κατηγοριών',
|
||||
'assistant.step.weather': 'Έλεγχος καιρού',
|
||||
'assistant.step.todos': 'Διαβάζω τη λίστα εργασιών',
|
||||
'assistant.step.costs': 'Έλεγχος εξόδων',
|
||||
'assistant.step.help': 'Αναζήτηση στη βοήθεια',
|
||||
'assistant.step.readingBooking': 'Διαβάζω την κράτηση',
|
||||
'assistant.step.working': 'Δουλεύω πάνω σε αυτό',
|
||||
'assistant.step.collapsed': '{count} βήματα',
|
||||
'assistant.step.collapsed.one': '{count} βήμα',
|
||||
'assistant.change.title': 'Προτεινόμενες αλλαγές',
|
||||
'assistant.change.doneTitle': 'Έγινε',
|
||||
'assistant.change.apply': 'Εφαρμογή',
|
||||
'assistant.change.applyN': 'Εφαρμογή {count}',
|
||||
'assistant.change.applyN.one': 'Εφαρμογή {count}',
|
||||
'assistant.change.discard': 'Απόρριψη',
|
||||
'assistant.change.include': 'Συμπερίληψη αυτής της αλλαγής',
|
||||
'assistant.change.applied': 'Εφαρμόστηκε',
|
||||
'assistant.change.discarded': 'Απορρίφθηκε',
|
||||
'assistant.change.failed': 'Δεν ήταν δυνατή η εφαρμογή.',
|
||||
'assistant.change.applying': 'Εφαρμογή σε εξέλιξη',
|
||||
'assistant.change.expired': 'Δεν είναι πλέον διαθέσιμο',
|
||||
'assistant.change.undo': 'Αναίρεση',
|
||||
'assistant.change.undone': 'Αναιρέθηκε',
|
||||
'assistant.change.undoStale': 'Άλλαξε στο μεταξύ. Προσαρμόστε το χειροκίνητα.',
|
||||
'assistant.change.undoFailed': 'Δεν ήταν δυνατή η αναίρεση.',
|
||||
'assistant.change.showOnMap': 'Εμφάνιση στον χάρτη',
|
||||
'assistant.change.empty': 'κενό',
|
||||
'assistant.change.pending': '{count} αλλαγές σας περιμένουν',
|
||||
'assistant.change.pending.one': '{count} αλλαγή σας περιμένει',
|
||||
'assistant.change.verb.addPlace': 'Προσθήκη μέρους',
|
||||
'assistant.change.verb.addPlaceToDay': 'Προσθήκη μέρους στην ημέρα',
|
||||
'assistant.change.verb.updatePlace': 'Αλλαγή μέρους',
|
||||
'assistant.change.verb.assignPlace': 'Προγραμματισμός μέρους σε ημέρα',
|
||||
'assistant.change.verb.moveAssignment': 'Μετακίνηση σε άλλη ημέρα',
|
||||
'assistant.change.verb.removeFromDay': 'Αφαίρεση από την ημέρα',
|
||||
'assistant.change.verb.addDayNote': 'Προσθήκη σημείωσης',
|
||||
'assistant.change.verb.updateDayNote': 'Αλλαγή σημείωσης',
|
||||
'assistant.change.verb.addPackingItem': 'Προσθήκη στη λίστα αποσκευών',
|
||||
'assistant.change.verb.updatePackingItem': 'Αλλαγή αντικειμένου αποσκευών',
|
||||
'assistant.change.verb.addTodo': 'Προσθήκη εργασίας',
|
||||
'assistant.change.verb.addBudgetItem': 'Προσθήκη εξόδου',
|
||||
'assistant.change.verb.addReservation': 'Προσθήκη κράτησης',
|
||||
'assistant.change.verb.importBooking': 'Προσθήκη κράτησης από αρχείο',
|
||||
'assistant.change.label.name': 'Όνομα',
|
||||
'assistant.change.label.description': 'Περιγραφή',
|
||||
'assistant.change.label.address': 'Διεύθυνση',
|
||||
'assistant.change.label.coordinates': 'Θέση στον χάρτη',
|
||||
'assistant.change.label.category': 'Κατηγορία',
|
||||
'assistant.change.label.day': 'Ημέρα',
|
||||
'assistant.change.label.time': 'Ώρα',
|
||||
'assistant.change.label.endTime': 'Έως',
|
||||
'assistant.change.label.notes': 'Σημειώσεις',
|
||||
'assistant.change.label.website': 'Ιστότοπος',
|
||||
'assistant.change.label.phone': 'Τηλέφωνο',
|
||||
'assistant.change.label.email': 'Ηλεκτρονικό ταχυδρομείο',
|
||||
'assistant.change.label.openingHours': 'Ωράριο λειτουργίας',
|
||||
'assistant.change.label.price': 'Τιμή',
|
||||
'assistant.change.label.currency': 'Νόμισμα',
|
||||
'assistant.change.label.stopType': 'Τύπος στάσης',
|
||||
'assistant.change.label.text': 'Κείμενο',
|
||||
'assistant.change.label.icon': 'Εικονίδιο',
|
||||
'assistant.change.label.color': 'Χρώμα',
|
||||
'assistant.change.label.position': 'Σειρά',
|
||||
'assistant.change.label.quantity': 'Ποσότητα',
|
||||
'assistant.change.label.weight': 'Βάρος',
|
||||
'assistant.change.label.bag': 'Τσάντα',
|
||||
'assistant.change.label.packed': 'Συσκευασμένο',
|
||||
'assistant.change.label.dueDate': 'Προθεσμία',
|
||||
'assistant.change.label.priority': 'Προτεραιότητα',
|
||||
'assistant.change.label.assignee': 'Ανατέθηκε σε',
|
||||
'assistant.change.label.type': 'Τύπος',
|
||||
'assistant.change.label.location': 'Τοποθεσία',
|
||||
'assistant.change.label.link': 'Σύνδεσμος',
|
||||
'assistant.change.label.confirmation': 'Αριθμός επιβεβαίωσης',
|
||||
'assistant.change.label.checkIn': 'Άφιξη',
|
||||
'assistant.change.label.checkOut': 'Αναχώρηση',
|
||||
'assistant.change.label.stay': 'Διαμονή',
|
||||
'assistant.change.label.members': 'Μοιρασμένο μεταξύ',
|
||||
'assistant.change.label.route': 'Διαδρομή',
|
||||
'assistant.history.open': 'Πρόσφατες συνομιλίες',
|
||||
'assistant.history.new': 'Νέα συνομιλία',
|
||||
'assistant.history.empty': 'Δεν υπάρχουν συνομιλίες ακόμη.',
|
||||
'assistant.history.retention': 'Οι συνομιλίες διαγράφονται μετά από {count} ημέρες.',
|
||||
'assistant.history.retention.one': 'Οι συνομιλίες διαγράφονται μετά από {count} ημέρα.',
|
||||
'assistant.history.forever': 'Οι συνομιλίες διατηρούνται μέχρι να τις διαγράψετε.',
|
||||
'assistant.error.notConnected': 'Ο βοηθός δεν έχει συνδεθεί ακόμη.',
|
||||
'assistant.error.busy': 'Εκτελείται ήδη σε άλλο παράθυρο.',
|
||||
'assistant.error.dailyLimit': 'Αυτά για σήμερα. Τα λέμε αύριο.',
|
||||
'assistant.error.rateLimited': 'Λίγο πιο αργά. Δοκιμάστε ξανά σε ένα λεπτό.',
|
||||
'assistant.error.needsReauth': 'Η σύνδεσή σας στο {provider} έληξε.',
|
||||
'assistant.error.usageLimit': 'Η χρήση του ChatGPT σας εξαντλήθηκε.',
|
||||
'assistant.error.notEligible': 'Αυτός ο λογαριασμός ChatGPT δεν μπορεί να χρησιμοποιηθεί εδώ.',
|
||||
'assistant.error.invalidKey': 'Το κλειδί σας για {provider} δεν λειτουργεί πια.',
|
||||
'assistant.error.providerDown': 'Το {provider} δεν αποκρίνεται αυτή τη στιγμή.',
|
||||
'assistant.error.maxSteps': 'Ήταν πολλά μαζί. Να συνεχίσω;',
|
||||
'assistant.error.offline': 'Εκτός σύνδεσης. Ο βοηθός χρειάζεται διαδίκτυο.',
|
||||
'assistant.error.noBookingsFound': 'Δεν βρέθηκε κράτηση σε αυτό το αρχείο.',
|
||||
'assistant.error.importUnavailable': 'Η ανάγνωση αρχείων κράτησης δεν είναι διαθέσιμη εδώ.',
|
||||
'assistant.error.generic': 'Κάτι πήγε στραβά.',
|
||||
'assistant.errorAction.retry': 'Δοκιμή ξανά',
|
||||
'assistant.errorAction.continueHere': 'Συνέχεια εδώ',
|
||||
'assistant.errorAction.continue': 'Συνέχεια',
|
||||
'assistant.errorAction.signIn': 'Νέα σύνδεση',
|
||||
'assistant.errorAction.viewUsage': 'Προβολή χρήσης',
|
||||
'assistant.errorAction.otherProvider': 'Επιλογή άλλου παρόχου',
|
||||
'assistant.errorAction.replaceKey': 'Αντικατάσταση κλειδιού',
|
||||
'assistant.errorAction.connectOwn': 'Σύνδεση δικού σας παρόχου',
|
||||
'assistant.admin.policy': 'Ποιος πληρώνει',
|
||||
'assistant.admin.policy.instance': 'Πληρώνω εγώ για όλους',
|
||||
'assistant.admin.policy.instanceOrUser': 'Πληρώνω εγώ, επιτρέπεται δικός τους',
|
||||
'assistant.admin.policy.user': 'Ο καθένας με τον δικό του λογαριασμό',
|
||||
'assistant.admin.policy.instanceHint':
|
||||
'Όλοι χρησιμοποιούν την παρακάτω σύνδεση και δεν μπορούν να προσθέσουν δική τους.',
|
||||
'assistant.admin.policy.instanceOrUserHint': 'Όλοι χρησιμοποιούν την παρακάτω σύνδεση, εκτός αν συνδέσουν δική τους.',
|
||||
'assistant.admin.policy.userHint': 'Ο καθένας συνδέει τον δικό του πάροχο. Δεν χρεώνεστε τίποτα.',
|
||||
'assistant.admin.connection': 'Σύνδεση για όλους',
|
||||
'assistant.admin.connectionNone': 'Δεν υπάρχει σύνδεση ακόμη.',
|
||||
'assistant.admin.connectionRemove': 'Αφαίρεση σύνδεσης',
|
||||
'assistant.admin.custom': 'Δικός σας διακομιστής',
|
||||
'assistant.admin.customHint': 'Οποιοσδήποτε διακομιστής συμβατός με OpenAI, για παράδειγμα Ollama.',
|
||||
'assistant.admin.baseUrl': 'Διεύθυνση διακομιστή',
|
||||
'assistant.admin.apiKeyOptional': 'Κλειδί API (προαιρετικό)',
|
||||
'assistant.admin.prefilled': 'Μεταφέρθηκε από την Ανάλυση με τεχνητή νοημοσύνη.',
|
||||
'assistant.admin.dailyLimit': 'Μηνύματα ανά άτομο ανά ημέρα',
|
||||
'assistant.admin.dailyLimitHint': 'Μετράει μόνο όσο πληρώνετε εσείς.',
|
||||
'assistant.admin.retention': 'Διατήρηση συνομιλιών',
|
||||
'assistant.admin.retentionDays': '{count} ημέρες',
|
||||
'assistant.admin.retentionDays.one': '{count} ημέρα',
|
||||
'assistant.admin.retentionForever': 'Για πάντα',
|
||||
'assistant.claude.title': 'Σύνδεση με Claude',
|
||||
'assistant.claude.description':
|
||||
'Χρησιμοποιήστε το TREK απευθείας μέσα στο Claude: προσθέστε το ως σύνδεσμο, συνδεθείτε, έτοιμο.',
|
||||
'assistant.claude.open': 'Άνοιγμα στο Claude',
|
||||
'assistant.claude.scan': 'Ή σαρώστε το με το κινητό σας',
|
||||
'assistant.claude.copyUrl': 'Αντιγραφή διεύθυνσης',
|
||||
'assistant.claude.qrAlt': 'Κωδικός QR για προσθήκη του TREK στο Claude',
|
||||
'assistant.claude.notPublic': 'Το Claude φτάνει στο TREK μόνο όταν είναι διαθέσιμο μέσω https στο διαδίκτυο.',
|
||||
'assistant.mobile.tile': 'Ρωτήστε το TREK',
|
||||
'assistant.mobile.tileHint': 'Σχεδιάστε με τον βοηθό',
|
||||
'assistant.mobile.backToChat': 'Πίσω στη συνομιλία',
|
||||
'assistant.step.nearbyAt': 'Αναζήτηση κοντά σε {place}',
|
||||
'assistant.step.searchingFor': 'Αναζήτηση για {query}',
|
||||
'assistant.step.weatherAt': 'Έλεγχος καιρού σε {place}',
|
||||
'assistant.change.timeline.show': 'Εμφάνιση προγράμματος',
|
||||
'assistant.change.timeline.hide': 'Απόκρυψη προγράμματος',
|
||||
'assistant.change.timeline.new': 'Νέο',
|
||||
'assistant.change.timeline.travel': '≈ {count} λεπτά διαδρομή',
|
||||
'assistant.change.timeline.travel.one': '≈ {count} λεπτό διαδρομή',
|
||||
'assistant.change.timeline.gap': '{count} λεπτά ελεύθερα',
|
||||
'assistant.change.timeline.gap.one': '{count} λεπτό ελεύθερο',
|
||||
'assistant.change.timeline.overlap': 'Επικαλύπτεται με {place}',
|
||||
'assistant.change.timeline.moreDays': 'Ακόμη {count} ημέρες',
|
||||
'assistant.change.timeline.moreDays.one': 'Ακόμη {count} ημέρα',
|
||||
'assistant.change.viewOnMap': 'Δείτε στον χάρτη',
|
||||
'assistant.change.before': 'Πριν',
|
||||
'assistant.change.after': 'Μετά',
|
||||
'assistant.change.compare': 'Σύγκριση στον χάρτη',
|
||||
'assistant.change.undoAll': 'Αναίρεση όλων',
|
||||
'assistant.change.undoneAll': 'Αναιρέθηκαν {count} αλλαγές',
|
||||
'assistant.change.undoneAll.one': 'Αναιρέθηκε {count} αλλαγή',
|
||||
'assistant.change.undoAllPartial': 'Αναιρέθηκαν: {done}, άλλαξαν στο μεταξύ: {stale}',
|
||||
'assistant.change.undoAllFailed': 'Δεν ήταν δυνατή η αναίρεση {count} αλλαγών',
|
||||
'assistant.change.undoAllFailed.one': 'Δεν ήταν δυνατή η αναίρεση {count} αλλαγής',
|
||||
'assistant.place.strip': 'Μέρη που βρέθηκαν',
|
||||
'assistant.place.addTo': 'Προσθήκη σε {day}',
|
||||
'assistant.place.addToShort': '+ Ημέρα {n}',
|
||||
'assistant.place.pickDay': 'Επιλογή ημέρας',
|
||||
'assistant.place.suggested': 'Προτάθηκε',
|
||||
'assistant.place.inTrip': 'Ήδη στο ταξίδι σας',
|
||||
'assistant.place.showOnMap': 'Στον χάρτη',
|
||||
'assistant.place.openNow': 'Ανοιχτό τώρα',
|
||||
'assistant.place.closed': 'Κλειστό',
|
||||
'assistant.place.fromPrev': '{distance} από την προηγούμενη στάση',
|
||||
'assistant.place.more': 'Ακόμη {count}',
|
||||
'assistant.place.more.one': 'Ακόμη {count}',
|
||||
'assistant.mention.days': 'Ημέρες',
|
||||
'assistant.mention.places': 'Μέρη',
|
||||
'assistant.mention.bookings': 'Κρατήσεις',
|
||||
'assistant.mention.none': 'Δεν βρέθηκε τίποτα',
|
||||
'assistant.mention.hint': 'Πληκτρολογήστε @ για να αναφέρετε ημέρες, μέρη και κρατήσεις.',
|
||||
'assistant.attach.dropPlace': 'Αφήστε το μέρος εδώ',
|
||||
'assistant.weather.strip': 'Καιρός για {count} ημέρες',
|
||||
'assistant.weather.strip.one': 'Καιρός για {count} ημέρα',
|
||||
'assistant.weather.rain': 'Βροχή {pct}%',
|
||||
'assistant.settings.followMap': 'Ο χάρτης ακολουθεί τον βοηθό',
|
||||
'assistant.settings.followMapHint': 'Ο χάρτης μετακινείται εκεί όπου ο βοηθός ψάχνει και προτείνει μέρη.',
|
||||
'assistant.error.decideFirst': 'Αποφασίστε πρώτα για τις εκκρεμείς προτάσεις.',
|
||||
'assistant.error.providerLimit': 'Συμπληρώθηκε το όριο στο {provider}. Δοκιμάστε ξανά αργότερα.',
|
||||
'assistant.hint.title': 'Αξίζει μια ματιά',
|
||||
'assistant.hint.dismiss': 'Απόκρυψη υπόδειξης',
|
||||
'assistant.hint.more': 'Ακόμη {count}',
|
||||
'assistant.hint.more.one': 'Ακόμη {count}',
|
||||
'assistant.hint.overlap': 'Τα {a} και {b} επικαλύπτονται στις {day}',
|
||||
'assistant.hint.overlap.prompt': 'Στις {day} επικαλύπτονται τα {a} και {b}. Πώς το λύνουμε;',
|
||||
'assistant.hint.closed': 'Το {place} είναι κλειστό στις {day}',
|
||||
'assistant.hint.closed.prompt': 'Το {place} είναι κλειστό στις {day}. Τι κάνουμε αντί γι’ αυτό;',
|
||||
'assistant.hint.opensLate': 'Το {place} ανοίγει μόλις στις {time} στις {day}',
|
||||
'assistant.hint.closesEarly': 'Το {place} κλείνει ήδη στις {time} στις {day}',
|
||||
'assistant.hint.outsideHours.prompt':
|
||||
'Η επίσκεψή μας στο {place} στις {day} δεν ταιριάζει με το ωράριο. Πώς τη μετακινούμε καλύτερα;',
|
||||
'assistant.hint.gap': '{day}: {count} ώρες ελεύθερες από τις {from}',
|
||||
'assistant.hint.gap.one': '{day}: {count} ώρα ελεύθερη από τις {from}',
|
||||
'assistant.hint.gap.prompt': 'Στις {day} δεν υπάρχει τίποτα από τις {from} ως τις {to}. Τι θα ταίριαζε εκεί;',
|
||||
'assistant.hint.longLeg': 'Τα {from} και {to} απέχουν {distance} σε ευθεία γραμμή',
|
||||
'assistant.hint.longLeg.prompt': 'Είναι πολύ μακριά από το {from} στο {to} στις {day}; Πώς πάμε καλύτερα;',
|
||||
'assistant.hint.tightLeg': 'Μόνο {count} λεπτά ανάμεσα σε {from} και {to}',
|
||||
'assistant.hint.tightLeg.one': 'Μόνο {count} λεπτό ανάμεσα σε {from} και {to}',
|
||||
'assistant.hint.tightLeg.prompt': 'Φτάνει ο χρόνος στις {day} για να πάμε από το {from} στο {to}; Τι να αλλάξουμε;',
|
||||
'assistant.hint.noStay': 'Ακόμη χωρίς κατάλυμα για {count} νύχτες από {day}',
|
||||
'assistant.hint.noStay.one': 'Ακόμη χωρίς κατάλυμα για {count} νύχτα από {day}',
|
||||
'assistant.hint.noStay.prompt': 'Μας λείπει ακόμη κατάλυμα από {day}. Τι προτείνετε;',
|
||||
'assistant.hint.unplacedBooking': 'Το {title} δεν είναι σε καμία ημέρα',
|
||||
'assistant.hint.unplacedBookingDay': 'Το {title} μάλλον ανήκει στις {day}',
|
||||
'assistant.hint.unplacedBookings': '{count} κρατήσεις δεν είναι σε καμία ημέρα',
|
||||
'assistant.hint.unplacedBookings.one': '{count} κράτηση δεν είναι σε καμία ημέρα',
|
||||
'assistant.hint.unplacedBooking.prompt': 'Ποιες κρατήσεις δεν είναι σε καμία ημέρα και πού ανήκουν;',
|
||||
'assistant.hint.menuOn': 'Εμφάνιση υποδείξεων',
|
||||
'assistant.hint.menuOff': 'Χωρίς υποδείξεις',
|
||||
'assistant.launcher.withHints': 'Βοηθός, {count} υποδείξεις',
|
||||
'assistant.launcher.withHints.one': 'Βοηθός, {count} υπόδειξη',
|
||||
'assistant.mobile.tileHints': '{count} σημεία για έλεγχο στο πρόγραμμά σας',
|
||||
'assistant.mobile.tileHints.one': '{count} σημείο για έλεγχο στο πρόγραμμά σας',
|
||||
'assistant.share.button': 'Κοινοποίηση στη συνομιλία του ταξιδιού',
|
||||
'assistant.share.title': 'Κοινοποίηση στη συνομιλία του ταξιδιού',
|
||||
'assistant.share.who': 'Όλοι στο ταξίδι θα δουν αυτό το μήνυμα, με σήμανση ότι είναι από τον βοηθό.',
|
||||
'assistant.share.includeChanges': 'Προσθήκη των αλλαγών που εφαρμόστηκαν',
|
||||
'assistant.share.changesHeading': 'Εφαρμόστηκαν:',
|
||||
'assistant.share.truncated': 'Συντομεύτηκε σε {count} χαρακτήρες',
|
||||
'assistant.share.truncated.one': 'Συντομεύτηκε σε {count} χαρακτήρα',
|
||||
'assistant.share.send': 'Κοινοποίηση',
|
||||
'assistant.share.done': 'Κοινοποιήθηκε στη συνομιλία του ταξιδιού',
|
||||
'assistant.share.shared': 'Κοινοποιήθηκε',
|
||||
'assistant.share.failed': 'Η κοινοποίηση απέτυχε. Δοκιμάστε ξανά σε λίγο.',
|
||||
'assistant.share.chatOff': 'Η συνομιλία του ταξιδιού είναι απενεργοποιημένη.',
|
||||
};
|
||||
export default assistant;
|
||||
@@ -106,5 +106,7 @@ const collab: TranslationStrings = {
|
||||
'collab.chat.attachImages': 'Επισύναψη εικόνων',
|
||||
'collab.chat.attachedImage': 'Συνημμένη εικόνα',
|
||||
'collab.chat.closeImage': 'Κλείσιμο εικόνας',
|
||||
'collab.chat.fromAssistant': 'από τον βοηθό',
|
||||
'collab.chat.fromAssistantHint': 'Ο/Η {name} κοινοποίησε αυτή την απάντηση του βοηθού',
|
||||
};
|
||||
export default collab;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import admin from './admin';
|
||||
import airport from './airport';
|
||||
import assistant from './assistant';
|
||||
import atlas from './atlas';
|
||||
import backup from './backup';
|
||||
import budget from './budget';
|
||||
@@ -116,5 +117,6 @@ const locale = {
|
||||
...dawarich,
|
||||
...docsync,
|
||||
...ferratas,
|
||||
...assistant,
|
||||
};
|
||||
export default locale;
|
||||
|
||||
@@ -88,6 +88,7 @@ const oauth: TranslationStrings = {
|
||||
'oauth.authorize.loginButton': 'Sign in to TREK', // en-fallback
|
||||
'oauth.authorize.requestLabel': 'Authorization Request', // en-fallback
|
||||
'oauth.authorize.requestDescription': 'This application is requesting access to your TREK account.', // en-fallback
|
||||
'oauth.authorize.verifiedHost': 'Επαληθευμένος τομέας: {host}',
|
||||
'oauth.authorize.trustNote': 'Only grant access to applications you trust. Your data stays on your server.', // en-fallback
|
||||
'oauth.authorize.selectScope': 'Select at least one scope', // en-fallback
|
||||
'oauth.authorize.approveScopes': 'Έγκριση ({count} δικαιώματα)',
|
||||
|
||||
@@ -647,6 +647,9 @@ const admin: TranslationStrings = {
|
||||
'admin.addons.catalog.llm_parsing.name': 'MI-elemzés',
|
||||
'admin.addons.catalog.llm_parsing.description':
|
||||
'Beolvassa azokat a foglalásokat, amelyeket a beépített értelmező nem tud, az általad választott MI-modellel',
|
||||
'admin.addons.catalog.ai_assistant.name': 'MI-asszisztens',
|
||||
'admin.addons.catalog.ai_assistant.description':
|
||||
'Csevegés minden utazásban, amely válaszol a kérdésekre, és módosításokat javasol, amelyeket te hagysz jóvá',
|
||||
'admin.addons.llm.vision.auto': 'Automatikus',
|
||||
'admin.addons.llm.vision.on': 'Igen',
|
||||
'admin.addons.llm.vision.off': 'Nem',
|
||||
|
||||
@@ -0,0 +1,347 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistant: TranslationStrings = {
|
||||
'assistant.launcher.tooltip': 'Asszisztens',
|
||||
'assistant.launcher.ctrlKey': 'Ctrl', // same-as-en
|
||||
'assistant.picker.title': 'Melyik utazás?',
|
||||
'assistant.picker.empty': 'Előbb hozz létre egy utazást, utána az asszisztens segít megtervezni.',
|
||||
'assistant.picker.now': 'Most',
|
||||
'assistant.picker.next': 'Következő',
|
||||
'assistant.window.title': 'Asszisztens',
|
||||
'assistant.window.log': 'Beszélgetés az asszisztenssel',
|
||||
'assistant.window.menu': 'További lehetőségek',
|
||||
'assistant.window.settings': 'MI-beállítások',
|
||||
'assistant.window.deleteThread': 'Csevegés törlése',
|
||||
'assistant.window.deleteConfirm': 'Törlöd ezt a csevegést? Ez nem vonható vissza.',
|
||||
'assistant.setup.question': 'Mivel szeretnél csevegni?',
|
||||
'assistant.setup.beta': 'Béta',
|
||||
'assistant.setup.chatgptNote': 'A hivatalos út egy API-kulcs. A ChatGPT-bejelentkezés még béta.',
|
||||
'assistant.setup.keySubtitle': 'Anthropic, OpenAI, Gemini vagy OpenRouter',
|
||||
'assistant.setup.adminOnly': 'Ezt az adminod állítja be.',
|
||||
'assistant.setup.forEveryone': 'Beállítás mindenkinek',
|
||||
'assistant.connect.title': 'MI-asszisztens',
|
||||
'assistant.connect.description':
|
||||
'Csevegj egy asszisztenssel, aki ismeri az utazásodat, és módosításokat javasol, amelyeket te hagysz jóvá.',
|
||||
'assistant.connect.chatgpt': 'Bejelentkezés ChatGPT-vel',
|
||||
'assistant.connect.chatgptHint': 'A ChatGPT-előfizetésedet használja. Nem kell kulcs.',
|
||||
'assistant.connect.chatgptStep1': 'Jelentkezz be a ChatGPT-be az új lapon.',
|
||||
'assistant.connect.chatgptOpen': 'ChatGPT-bejelentkezés megnyitása',
|
||||
'assistant.connect.chatgptStep2':
|
||||
'Ezután a böngésző egy hibaoldalt mutat. Ez így rendben van. Másold ki a fenti címet, és gyere vissza.',
|
||||
'assistant.connect.chatgptPaste': 'Cím beillesztése',
|
||||
'assistant.connect.chatgptWrong': 'Ez nem a megfelelő cím. 127.0.0.1-gyel kezdődik.',
|
||||
'assistant.connect.chatgptExpired': 'Ez túl sokáig tartott. Jelentkezz be újra.',
|
||||
'assistant.connect.chatgptOtherAccount': 'Ez egy másik ChatGPT-fiók. Előbb távolítsd el a kapcsolatot.',
|
||||
'assistant.connect.key': 'API-kulcs beillesztése',
|
||||
'assistant.connect.keyPlaceholder': 'Illeszd be ide a kulcsot',
|
||||
'assistant.connect.detected': 'Felismerve: {provider}',
|
||||
'assistant.connect.whichProvider': 'Melyik szolgáltatótól van ez a kulcs?',
|
||||
'assistant.connect.testing': 'Kapcsolat ellenőrzése',
|
||||
'assistant.connect.ok': 'Kapcsolódva. Indulhatunk.',
|
||||
'assistant.connect.openAssistant': 'Asszisztens megnyitása',
|
||||
'assistant.connect.badKey': 'Ez a kulcs nem működik.',
|
||||
'assistant.connect.unknownModel': 'Ez a modell itt nem érhető el.',
|
||||
'assistant.connect.getKey': 'Kulcs igénylése',
|
||||
'assistant.connect.hintAnthropic': 'A Claude Max és Team csomag API-kreditet is tartalmaz.',
|
||||
'assistant.connect.hintOpenai': 'Hozz létre egy kulcsot az OpenAI irányítópultján.',
|
||||
'assistant.connect.hintGemini': 'Az AI Studióban van percenkénti és napi korlátos ingyenes szint, amelyben a Google felhasználhatja, amit küldesz, a termékei fejlesztésére. Bekapcsolt számlázással magasabbak a korlátok, és ez nem érvényes.',
|
||||
'assistant.connect.hintOpenrouter': 'Jelentkezz be az OpenRouterrel, nem kell kulcsot másolni.',
|
||||
'assistant.connect.openrouter': 'OpenRouter csatlakoztatása',
|
||||
'assistant.connect.save': 'Csatlakoztatás',
|
||||
'assistant.connect.model': 'Modell',
|
||||
'assistant.connect.modelHint': 'Csak az eszközöket használni tudó modellek jelennek meg.',
|
||||
'assistant.connect.free': 'Ingyenes szint elérhető',
|
||||
'assistant.connect.paid': 'Használat alapján',
|
||||
'assistant.model.recommended': 'Ajánlott',
|
||||
'assistant.model.free': 'Ingyenes',
|
||||
'assistant.connect.connected': 'Kapcsolódva: {provider}',
|
||||
'assistant.connect.providedByAdmin': 'Az adminod biztosítja',
|
||||
'assistant.connect.disconnect': 'Leválasztás',
|
||||
'assistant.connect.disconnectConfirm': 'Leválasztod: {provider}? A csevegéseid megmaradnak.',
|
||||
'assistant.connect.signOut': 'Kijelentkezés',
|
||||
'assistant.connect.statusOk': 'Kapcsolódva',
|
||||
'assistant.connect.statusNeedsReauth': 'A bejelentkezés lejárt',
|
||||
'assistant.connect.statusSignedOut': 'Kijelentkezve',
|
||||
'assistant.connect.statusError': 'Nem érhető el',
|
||||
'assistant.connect.quota': 'Ma {used} / {count} üzenet felhasználva',
|
||||
'assistant.connect.quota.one': 'Ma {used} / {count} üzenet felhasználva',
|
||||
'assistant.connect.claudeLink': 'Claude-ot használsz? Csatlakozz a Claude-ban',
|
||||
'assistant.privacy.what': 'Mi kerül megosztásra?',
|
||||
'assistant.privacy.banner':
|
||||
'Az üzeneteid és az utazás adatai, amelyeket az asszisztens elolvas, ide kerülnek: {provider}.',
|
||||
'assistant.privacy.ok': 'Értem',
|
||||
'assistant.privacy.shared':
|
||||
'Az üzeneteid és az utazás részletei, amelyeket az asszisztens elolvas, például helyek, napok és foglalások.',
|
||||
'assistant.privacy.notShared': 'A jelszavad, a fájljaid és a többi utazásod nem.',
|
||||
'assistant.privacy.provider': 'A(z) {provider} ezt a saját adatvédelmi feltételei szerint kezeli.',
|
||||
'assistant.empty.title': 'Mit tervezünk?',
|
||||
'assistant.empty.readOnly':
|
||||
'Ezt az utazást csak megtekintheted, ezért kérdésekre válaszolok, de semmit nem módosítok.',
|
||||
'assistant.suggest.planFirstDays': 'Tervezd meg nekem az első napokat',
|
||||
'assistant.suggest.mustSee': 'Mit nem szabad itt kihagynom?',
|
||||
'assistant.suggest.packingList': 'Készíts csomaglistát ehhez az utazáshoz',
|
||||
'assistant.suggest.emptyDays': 'Melyik napok üresek még?',
|
||||
'assistant.suggest.rainPlan': 'Mit csinálhatunk, ha esik?',
|
||||
'assistant.suggest.today': 'Mi a mai program?',
|
||||
'assistant.suggest.eatNearby': 'Keress egy helyet a közelben, ahol ehetünk',
|
||||
'assistant.suggest.weather': 'Milyen időre számíthatunk?',
|
||||
'assistant.suggest.openTodos': 'Mi van még nyitva a teendőlistán?',
|
||||
'assistant.suggest.settleUp': 'Ki kinek tartozik?',
|
||||
'assistant.suggest.recap': 'Foglald össze az utazásunkat',
|
||||
'assistant.suggest.nearPlace': 'Mi van még {place} közelében?',
|
||||
'assistant.suggest.placeTips': 'Tippek a látogatáshoz: {place}',
|
||||
'assistant.suggest.planDay': 'Tervezd meg nekem ezt a napot: {day}',
|
||||
'assistant.suggest.dayTooFull': 'Túl zsúfolt ez a nap: {day}?',
|
||||
'assistant.thread.scrollDown': 'Ugrás a legújabb üzenethez',
|
||||
'assistant.attach.button': 'Foglalás csatolása',
|
||||
'assistant.attach.drop': 'Engedd el itt, és elolvasom a foglalást',
|
||||
'assistant.attach.unsupported': 'Ezt a fájltípust nem tudom olvasni. Próbálj PDF-et, e-mailt, képet vagy Wallet-kártyát.',
|
||||
'assistant.attach.tooLarge': 'Ez a fájl nagyobb 10 MB-nál.',
|
||||
'assistant.composer.placeholder': 'Kérdezz bármit erről: {trip}',
|
||||
'assistant.composer.send': 'Küldés',
|
||||
'assistant.composer.stop': 'Leállítás',
|
||||
'assistant.composer.decideFirst': 'Előbb alkalmazd vagy vesd el a fenti módosításokat.',
|
||||
'assistant.context.ask': 'Kérdezz',
|
||||
'assistant.context.remove': 'Kontextus eltávolítása',
|
||||
'assistant.step.reading': 'A terv olvasása',
|
||||
'assistant.step.searching': 'Helyek keresése',
|
||||
'assistant.step.details': 'Részletek lekérése',
|
||||
'assistant.step.nearby': 'Keresés a közelben',
|
||||
'assistant.step.categories': 'Kategóriák ellenőrzése',
|
||||
'assistant.step.weather': 'Az időjárás ellenőrzése',
|
||||
'assistant.step.todos': 'A teendőlista olvasása',
|
||||
'assistant.step.costs': 'A költségek ellenőrzése',
|
||||
'assistant.step.help': 'Utánanézés a súgóban',
|
||||
'assistant.step.readingBooking': 'A foglalás olvasása',
|
||||
'assistant.step.working': 'Dolgozom rajta',
|
||||
'assistant.step.collapsed': '{count} lépés',
|
||||
'assistant.step.collapsed.one': '{count} lépés',
|
||||
'assistant.change.title': 'Javasolt módosítások',
|
||||
'assistant.change.doneTitle': 'Kész',
|
||||
'assistant.change.apply': 'Alkalmazás',
|
||||
'assistant.change.applyN': '{count} alkalmazása',
|
||||
'assistant.change.applyN.one': '{count} alkalmazása',
|
||||
'assistant.change.discard': 'Elvetés',
|
||||
'assistant.change.include': 'Ez a módosítás is legyen benne',
|
||||
'assistant.change.applied': 'Alkalmazva',
|
||||
'assistant.change.discarded': 'Elvetve',
|
||||
'assistant.change.failed': 'Nem sikerült alkalmazni.',
|
||||
'assistant.change.applying': 'Alkalmazás folyamatban',
|
||||
'assistant.change.expired': 'Már nem érhető el',
|
||||
'assistant.change.undo': 'Visszavonás',
|
||||
'assistant.change.undone': 'Visszavonva',
|
||||
'assistant.change.undoStale': 'Időközben megváltozott. Kérlek, igazítsd kézzel.',
|
||||
'assistant.change.undoFailed': 'Nem sikerült visszavonni.',
|
||||
'assistant.change.showOnMap': 'Megjelenítés a térképen',
|
||||
'assistant.change.empty': 'üres',
|
||||
'assistant.change.pending': '{count} módosítás vár rád',
|
||||
'assistant.change.pending.one': '{count} módosítás vár rád',
|
||||
'assistant.change.verb.addPlace': 'Hely hozzáadása',
|
||||
'assistant.change.verb.addPlaceToDay': 'Hely hozzáadása a naphoz',
|
||||
'assistant.change.verb.updatePlace': 'Hely módosítása',
|
||||
'assistant.change.verb.assignPlace': 'Hely beosztása egy napra',
|
||||
'assistant.change.verb.moveAssignment': 'Áthelyezés másik napra',
|
||||
'assistant.change.verb.removeFromDay': 'Levétel a napról',
|
||||
'assistant.change.verb.addDayNote': 'Jegyzet hozzáadása',
|
||||
'assistant.change.verb.updateDayNote': 'Jegyzet módosítása',
|
||||
'assistant.change.verb.addPackingItem': 'Hozzáadás a csomaglistához',
|
||||
'assistant.change.verb.updatePackingItem': 'Csomagolási tétel módosítása',
|
||||
'assistant.change.verb.addTodo': 'Teendő hozzáadása',
|
||||
'assistant.change.verb.addBudgetItem': 'Kiadás hozzáadása',
|
||||
'assistant.change.verb.addReservation': 'Foglalás hozzáadása',
|
||||
'assistant.change.verb.importBooking': 'Foglalás hozzáadása fájlból',
|
||||
'assistant.change.label.name': 'Név',
|
||||
'assistant.change.label.description': 'Leírás',
|
||||
'assistant.change.label.address': 'Cím',
|
||||
'assistant.change.label.coordinates': 'Hely a térképen',
|
||||
'assistant.change.label.category': 'Kategória',
|
||||
'assistant.change.label.day': 'Nap',
|
||||
'assistant.change.label.time': 'Időpont',
|
||||
'assistant.change.label.endTime': 'Eddig',
|
||||
'assistant.change.label.notes': 'Jegyzetek',
|
||||
'assistant.change.label.website': 'Weboldal',
|
||||
'assistant.change.label.phone': 'Telefon',
|
||||
'assistant.change.label.email': 'E-mail',
|
||||
'assistant.change.label.openingHours': 'Nyitvatartás',
|
||||
'assistant.change.label.price': 'Ár',
|
||||
'assistant.change.label.currency': 'Pénznem',
|
||||
'assistant.change.label.stopType': 'Megálló típusa',
|
||||
'assistant.change.label.text': 'Szöveg',
|
||||
'assistant.change.label.icon': 'Ikon',
|
||||
'assistant.change.label.color': 'Szín',
|
||||
'assistant.change.label.position': 'Pozíció',
|
||||
'assistant.change.label.quantity': 'Mennyiség',
|
||||
'assistant.change.label.weight': 'Súly',
|
||||
'assistant.change.label.bag': 'Táska',
|
||||
'assistant.change.label.packed': 'Becsomagolva',
|
||||
'assistant.change.label.dueDate': 'Határidő',
|
||||
'assistant.change.label.priority': 'Prioritás',
|
||||
'assistant.change.label.assignee': 'Felelős',
|
||||
'assistant.change.label.type': 'Típus',
|
||||
'assistant.change.label.location': 'Helyszín',
|
||||
'assistant.change.label.link': 'Hivatkozás',
|
||||
'assistant.change.label.confirmation': 'Visszaigazolási szám',
|
||||
'assistant.change.label.checkIn': 'Érkezés',
|
||||
'assistant.change.label.checkOut': 'Távozás',
|
||||
'assistant.change.label.stay': 'Szállás',
|
||||
'assistant.change.label.members': 'Megosztva köztük',
|
||||
'assistant.change.label.route': 'Útvonal',
|
||||
'assistant.history.open': 'Legutóbbi csevegések',
|
||||
'assistant.history.new': 'Új csevegés',
|
||||
'assistant.history.empty': 'Még nincs csevegés.',
|
||||
'assistant.history.retention': 'A csevegések {count} nap után törlődnek.',
|
||||
'assistant.history.retention.one': 'A csevegések {count} nap után törlődnek.',
|
||||
'assistant.history.forever': 'A csevegések megmaradnak, amíg nem törlöd őket.',
|
||||
'assistant.error.notConnected': 'Az asszisztens még nincs csatlakoztatva.',
|
||||
'assistant.error.busy': 'Már fut egy másik ablakban.',
|
||||
'assistant.error.dailyLimit': 'Mára ennyi volt. Holnap folytatjuk.',
|
||||
'assistant.error.rateLimited': 'Egy kicsit lassabban. Próbáld újra egy perc múlva.',
|
||||
'assistant.error.needsReauth': 'A(z) {provider} bejelentkezésed lejárt.',
|
||||
'assistant.error.usageLimit': 'Elfogyott a ChatGPT-kereted.',
|
||||
'assistant.error.notEligible': 'Ez a ChatGPT-fiók itt nem használható.',
|
||||
'assistant.error.invalidKey': 'A(z) {provider} kulcsod már nem működik.',
|
||||
'assistant.error.providerDown': 'A(z) {provider} most nem válaszol.',
|
||||
'assistant.error.maxSteps': 'Ez egyszerre sok volt. Folytassam?',
|
||||
'assistant.error.offline': 'Offline vagy. Az asszisztensnek internet kell.',
|
||||
'assistant.error.noBookingsFound': 'Ebben a fájlban nem található foglalás.',
|
||||
'assistant.error.importUnavailable': 'A foglalási fájlok olvasása itt nem érhető el.',
|
||||
'assistant.error.generic': 'Valami hiba történt.',
|
||||
'assistant.errorAction.retry': 'Újra',
|
||||
'assistant.errorAction.continueHere': 'Folytatás itt',
|
||||
'assistant.errorAction.continue': 'Folytatás',
|
||||
'assistant.errorAction.signIn': 'Újra bejelentkezés',
|
||||
'assistant.errorAction.viewUsage': 'Felhasználás megtekintése',
|
||||
'assistant.errorAction.otherProvider': 'Másik szolgáltató választása',
|
||||
'assistant.errorAction.replaceKey': 'Kulcs cseréje',
|
||||
'assistant.errorAction.connectOwn': 'Saját szolgáltató csatlakoztatása',
|
||||
'assistant.admin.policy': 'Ki fizet',
|
||||
'assistant.admin.policy.instance': 'Én fizetek mindenkiért',
|
||||
'assistant.admin.policy.instanceOrUser': 'Én fizetek, saját is engedélyezett',
|
||||
'assistant.admin.policy.user': 'Mindenki a saját fiókját használja',
|
||||
'assistant.admin.policy.instanceHint': 'Mindenki az alábbi kapcsolatot használja, és nem adhat hozzá sajátot.',
|
||||
'assistant.admin.policy.instanceOrUserHint':
|
||||
'Mindenki az alábbi kapcsolatot használja, hacsak nem csatlakoztat sajátot.',
|
||||
'assistant.admin.policy.userHint': 'Mindenki a saját szolgáltatóját csatlakoztatja. Neked nem kerül semmibe.',
|
||||
'assistant.admin.connection': 'Kapcsolat mindenkinek',
|
||||
'assistant.admin.connectionNone': 'Még nincs kapcsolat.',
|
||||
'assistant.admin.connectionRemove': 'Kapcsolat eltávolítása',
|
||||
'assistant.admin.custom': 'Saját szerver',
|
||||
'assistant.admin.customHint': 'Bármely OpenAI-kompatibilis szerver, például Ollama.',
|
||||
'assistant.admin.baseUrl': 'Szerver címe',
|
||||
'assistant.admin.apiKeyOptional': 'API-kulcs (nem kötelező)',
|
||||
'assistant.admin.prefilled': 'Átvéve az MI-elemzésből.',
|
||||
'assistant.admin.dailyLimit': 'Üzenetek személyenként naponta',
|
||||
'assistant.admin.dailyLimitHint': 'Csak addig számít, amíg te fizetsz.',
|
||||
'assistant.admin.retention': 'Csevegések megőrzése',
|
||||
'assistant.admin.retentionDays': '{count} nap',
|
||||
'assistant.admin.retentionDays.one': '{count} nap',
|
||||
'assistant.admin.retentionForever': 'Örökre',
|
||||
'assistant.claude.title': 'Kapcsolódás a Claude-dal',
|
||||
'assistant.claude.description':
|
||||
'Használd a TREK-et közvetlenül a Claude-ban: add hozzá összekötőként, jelentkezz be, kész.',
|
||||
'assistant.claude.open': 'Megnyitás a Claude-ban',
|
||||
'assistant.claude.scan': 'Vagy olvasd be a telefonoddal',
|
||||
'assistant.claude.copyUrl': 'Cím másolása',
|
||||
'assistant.claude.qrAlt': 'QR-kód a TREK hozzáadásához a Claude-ban',
|
||||
'assistant.claude.notPublic':
|
||||
'A Claude csak akkor éri el a TREK-et, ha az https-en keresztül elérhető az interneten.',
|
||||
'assistant.mobile.tile': 'Kérdezd a TREK-et',
|
||||
'assistant.mobile.tileHint': 'Tervezz az asszisztenssel',
|
||||
'assistant.mobile.backToChat': 'Vissza a csevegéshez',
|
||||
'assistant.step.nearbyAt': 'Keresés itt: {place} környéke',
|
||||
'assistant.step.searchingFor': 'Keresés: {query}',
|
||||
'assistant.step.weatherAt': 'Időjárás ellenőrzése: {place}',
|
||||
'assistant.change.timeline.show': 'Menetrend mutatása',
|
||||
'assistant.change.timeline.hide': 'Menetrend elrejtése',
|
||||
'assistant.change.timeline.new': 'Új',
|
||||
'assistant.change.timeline.travel': '≈ {count} perc út',
|
||||
'assistant.change.timeline.travel.one': '≈ {count} perc út',
|
||||
'assistant.change.timeline.gap': '{count} perc szabad',
|
||||
'assistant.change.timeline.gap.one': '{count} perc szabad',
|
||||
'assistant.change.timeline.overlap': 'Ütközik ezzel: {place}',
|
||||
'assistant.change.timeline.moreDays': 'További {count} nap',
|
||||
'assistant.change.timeline.moreDays.one': 'További {count} nap',
|
||||
'assistant.change.viewOnMap': 'Megnézem a térképen',
|
||||
'assistant.change.before': 'Előtte',
|
||||
'assistant.change.after': 'Utána',
|
||||
'assistant.change.compare': 'Összehasonlítás a térképen',
|
||||
'assistant.change.undoAll': 'Összes visszavonása',
|
||||
'assistant.change.undoneAll': '{count} módosítás visszavonva',
|
||||
'assistant.change.undoneAll.one': '{count} módosítás visszavonva',
|
||||
'assistant.change.undoAllPartial': 'Visszavonva: {done}; közben módosult: {stale}',
|
||||
'assistant.change.undoAllFailed': '{count} módosítást nem sikerült visszavonni',
|
||||
'assistant.change.undoAllFailed.one': '{count} módosítást nem sikerült visszavonni',
|
||||
'assistant.place.strip': 'Talált helyek',
|
||||
'assistant.place.addTo': 'Hozzáadás ehhez: {day}',
|
||||
'assistant.place.addToShort': '+ {n}. nap',
|
||||
'assistant.place.pickDay': 'Nap választása',
|
||||
'assistant.place.suggested': 'Javasolva',
|
||||
'assistant.place.inTrip': 'Már az utadban van',
|
||||
'assistant.place.showOnMap': 'Térképen',
|
||||
'assistant.place.openNow': 'Most nyitva',
|
||||
'assistant.place.closed': 'Zárva',
|
||||
'assistant.place.fromPrev': '{distance} az előző megállótól',
|
||||
'assistant.place.more': 'Még {count}',
|
||||
'assistant.place.more.one': 'Még {count}',
|
||||
'assistant.mention.days': 'Napok',
|
||||
'assistant.mention.places': 'Helyek',
|
||||
'assistant.mention.bookings': 'Foglalások',
|
||||
'assistant.mention.none': 'Nincs találat',
|
||||
'assistant.mention.hint': 'Írj @ jelet, hogy napokat, helyeket és foglalásokat említs.',
|
||||
'assistant.attach.dropPlace': 'Ide engedd el a helyet',
|
||||
'assistant.weather.strip': 'Időjárás {count} napra',
|
||||
'assistant.weather.strip.one': 'Időjárás {count} napra',
|
||||
'assistant.weather.rain': '{pct}% eső',
|
||||
'assistant.settings.followMap': 'A térkép követi az asszisztenst',
|
||||
'assistant.settings.followMapHint': 'A térkép oda mozdul, ahol az asszisztens keres és helyeket javasol.',
|
||||
'assistant.error.decideFirst': 'Előbb dönts a nyitott javaslatokról.',
|
||||
'assistant.error.providerLimit': 'Elérted a korlátot itt: {provider}. Próbáld újra később.',
|
||||
'assistant.hint.title': 'Érdemes megnézni',
|
||||
'assistant.hint.dismiss': 'Tipp elrejtése',
|
||||
'assistant.hint.more': 'Még {count}',
|
||||
'assistant.hint.more.one': 'Még {count}',
|
||||
'assistant.hint.overlap': '{a} és {b} ütközik ekkor: {day}',
|
||||
'assistant.hint.overlap.prompt': '{day}: {a} és {b} ütközik. Hogyan oldjuk meg?',
|
||||
'assistant.hint.closed': '{place} zárva van ekkor: {day}',
|
||||
'assistant.hint.closed.prompt': '{place} zárva van ekkor: {day}. Mit csináljunk helyette?',
|
||||
'assistant.hint.opensLate': '{place} csak {time}-kor nyit ekkor: {day}',
|
||||
'assistant.hint.closesEarly': '{place} már {time}-kor zár ekkor: {day}',
|
||||
'assistant.hint.outsideHours.prompt':
|
||||
'A látogatásunk itt: {place} ({day}) nem fér bele a nyitvatartásba. Hogyan tegyük át a legjobban?',
|
||||
'assistant.hint.gap': '{day}: {count} óra szabad {from} órától',
|
||||
'assistant.hint.gap.one': '{day}: {count} óra szabad {from} órától',
|
||||
'assistant.hint.gap.prompt': '{day}: {from} és {to} között nincs semmi tervezve. Mi férne bele?',
|
||||
'assistant.hint.longLeg': '{from} és {to} légvonalban {distance} távolságra van',
|
||||
'assistant.hint.longLeg.prompt': 'Túl messze van {from} és {to} ekkor: {day}? Hogyan jutunk el oda a legjobban?',
|
||||
'assistant.hint.tightLeg': 'Csak {count} perc {from} és {to} között',
|
||||
'assistant.hint.tightLeg.one': 'Csak {count} perc {from} és {to} között',
|
||||
'assistant.hint.tightLeg.prompt':
|
||||
'Elég az idő {day} napon, hogy {from} helyről {to} helyre érjünk? Min változtassunk?',
|
||||
'assistant.hint.noStay': 'Még nincs szállás {count} éjszakára ettől: {day}',
|
||||
'assistant.hint.noStay.one': 'Még nincs szállás {count} éjszakára ettől: {day}',
|
||||
'assistant.hint.noStay.prompt': 'Ettől a naptól még hiányzik a szállás: {day}. Mit javasolsz?',
|
||||
'assistant.hint.unplacedBooking': '{title} még egyik naphoz sincs rendelve',
|
||||
'assistant.hint.unplacedBookingDay': '{title} valószínűleg ide tartozik: {day}',
|
||||
'assistant.hint.unplacedBookings': '{count} foglalás még egyik naphoz sincs rendelve',
|
||||
'assistant.hint.unplacedBookings.one': '{count} foglalás még egyik naphoz sincs rendelve',
|
||||
'assistant.hint.unplacedBooking.prompt': 'Mely foglalások nincsenek még naphoz rendelve, és hová tartoznak?',
|
||||
'assistant.hint.menuOn': 'Tippek mutatása',
|
||||
'assistant.hint.menuOff': 'Tippek elrejtése',
|
||||
'assistant.launcher.withHints': 'Asszisztens, {count} tipp',
|
||||
'assistant.launcher.withHints.one': 'Asszisztens, {count} tipp',
|
||||
'assistant.mobile.tileHints': '{count} dolog, amit érdemes megnézni a tervedben',
|
||||
'assistant.mobile.tileHints.one': '{count} dolog, amit érdemes megnézni a tervedben',
|
||||
'assistant.share.button': 'Megosztás az út csevegésében',
|
||||
'assistant.share.title': 'Megosztás az út csevegésében',
|
||||
'assistant.share.who': 'Az út minden résztvevője látja ezt az üzenetet, az asszisztenstől származóként jelölve.',
|
||||
'assistant.share.includeChanges': 'Az alkalmazott módosítások csatolása',
|
||||
'assistant.share.changesHeading': 'Alkalmazva:',
|
||||
'assistant.share.truncated': '{count} karakterre rövidítve',
|
||||
'assistant.share.truncated.one': '{count} karakterre rövidítve',
|
||||
'assistant.share.send': 'Megosztás',
|
||||
'assistant.share.done': 'Megosztva az út csevegésében',
|
||||
'assistant.share.shared': 'Megosztva',
|
||||
'assistant.share.failed': 'A megosztás nem sikerült. Próbáld újra egy pillanat múlva.',
|
||||
'assistant.share.chatOff': 'Az út csevegése ki van kapcsolva.',
|
||||
};
|
||||
export default assistant;
|
||||
@@ -106,5 +106,7 @@ const collab: TranslationStrings = {
|
||||
'collab.chat.attachImages': 'Képek csatolása',
|
||||
'collab.chat.attachedImage': 'Csatolt kép',
|
||||
'collab.chat.closeImage': 'Kép bezárása',
|
||||
'collab.chat.fromAssistant': 'az asszisztenstől',
|
||||
'collab.chat.fromAssistantHint': '{name} megosztotta az asszisztens válaszát',
|
||||
};
|
||||
export default collab;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import admin from './admin';
|
||||
import airport from './airport';
|
||||
import assistant from './assistant';
|
||||
import atlas from './atlas';
|
||||
import backup from './backup';
|
||||
import budget from './budget';
|
||||
@@ -116,5 +117,6 @@ const locale = {
|
||||
...dawarich,
|
||||
...docsync,
|
||||
...ferratas,
|
||||
...assistant,
|
||||
};
|
||||
export default locale;
|
||||
|
||||
@@ -86,6 +86,7 @@ const oauth: TranslationStrings = {
|
||||
'oauth.authorize.loginButton': 'Sign in to TREK', // en-fallback
|
||||
'oauth.authorize.requestLabel': 'Authorization Request', // en-fallback
|
||||
'oauth.authorize.requestDescription': 'This application is requesting access to your TREK account.', // en-fallback
|
||||
'oauth.authorize.verifiedHost': 'Ellenőrzött domain: {host}',
|
||||
'oauth.authorize.trustNote': 'Only grant access to applications you trust. Your data stays on your server.', // en-fallback
|
||||
'oauth.authorize.selectScope': 'Select at least one scope', // en-fallback
|
||||
'oauth.authorize.approveScopes': 'Jóváhagyás ({count} jogosultság)',
|
||||
|
||||
@@ -37,6 +37,7 @@ describe('i18n untranslated ratchet', () => {
|
||||
'https://ntfy.sh',
|
||||
'your@email.com',
|
||||
'<b>{name}</b>',
|
||||
'Ctrl',
|
||||
]) {
|
||||
expect([same, isInvariant(same)]).toEqual([same, true]);
|
||||
}
|
||||
|
||||
@@ -688,6 +688,9 @@ const admin: TranslationStrings = {
|
||||
'admin.addons.catalog.llm_parsing.name': 'Analisis AI',
|
||||
'admin.addons.catalog.llm_parsing.description':
|
||||
'Membaca pemesanan yang tidak dipahami pengurai bawaan, dengan model AI pilihanmu',
|
||||
'admin.addons.catalog.ai_assistant.name': 'Asisten AI',
|
||||
'admin.addons.catalog.ai_assistant.description':
|
||||
'Obrolan di setiap perjalanan yang menjawab pertanyaan dan menyarankan perubahan yang kamu konfirmasi',
|
||||
'admin.addons.llm.vision.auto': 'Otomatis',
|
||||
'admin.addons.llm.vision.on': 'Ya',
|
||||
'admin.addons.llm.vision.off': 'Tidak',
|
||||
|
||||
@@ -0,0 +1,323 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistant: TranslationStrings = {
|
||||
'assistant.launcher.tooltip': 'Asisten',
|
||||
'assistant.launcher.ctrlKey': 'Ctrl', // same-as-en
|
||||
'assistant.picker.title': 'Perjalanan yang mana?',
|
||||
'assistant.picker.empty': 'Buat perjalanan dulu, lalu asisten bisa membantumu merencanakannya.',
|
||||
'assistant.picker.now': 'Sekarang',
|
||||
'assistant.picker.next': 'Berikutnya',
|
||||
'assistant.window.title': 'Asisten',
|
||||
'assistant.window.log': 'Percakapan dengan asisten',
|
||||
'assistant.window.menu': 'Opsi lainnya',
|
||||
'assistant.window.settings': 'Pengaturan AI',
|
||||
'assistant.window.deleteThread': 'Hapus obrolan',
|
||||
'assistant.window.deleteConfirm': 'Hapus obrolan ini? Tindakan ini tidak bisa dibatalkan.',
|
||||
'assistant.setup.question': 'Kamu ingin mengobrol dengan apa?',
|
||||
'assistant.setup.beta': 'Beta', // same-as-en
|
||||
'assistant.setup.chatgptNote': 'Kunci API adalah cara resminya. Masuk dengan ChatGPT masih dalam tahap beta.',
|
||||
'assistant.setup.keySubtitle': 'Anthropic, OpenAI, Gemini, atau OpenRouter',
|
||||
'assistant.setup.adminOnly': 'Admin kamu yang menyiapkan ini.',
|
||||
'assistant.setup.forEveryone': 'Siapkan untuk semua orang',
|
||||
'assistant.connect.title': 'Asisten AI',
|
||||
'assistant.connect.description':
|
||||
'Mengobrol dengan asisten yang mengenal perjalananmu dan menyarankan perubahan yang kamu konfirmasi.',
|
||||
'assistant.connect.chatgpt': 'Masuk dengan ChatGPT',
|
||||
'assistant.connect.chatgptHint': 'Memakai paket ChatGPT kamu. Tidak perlu kunci.',
|
||||
'assistant.connect.chatgptStep1': 'Masuk ke ChatGPT di tab baru.',
|
||||
'assistant.connect.chatgptOpen': 'Buka halaman masuk ChatGPT',
|
||||
'assistant.connect.chatgptStep2':
|
||||
'Setelah itu browser akan menampilkan halaman error. Itu memang seharusnya. Salin alamat di bagian atas, lalu kembali ke sini.',
|
||||
'assistant.connect.chatgptPaste': 'Tempel alamat',
|
||||
'assistant.connect.chatgptWrong': 'Itu bukan alamat yang benar. Alamatnya diawali 127.0.0.1.',
|
||||
'assistant.connect.chatgptExpired': 'Terlalu lama. Silakan masuk lagi.',
|
||||
'assistant.connect.chatgptOtherAccount': 'Ini akun ChatGPT yang berbeda. Hapus koneksinya terlebih dahulu.',
|
||||
'assistant.connect.key': 'Tempel kunci API',
|
||||
'assistant.connect.keyPlaceholder': 'Tempel kuncimu di sini',
|
||||
'assistant.connect.detected': 'Terdeteksi: {provider}',
|
||||
'assistant.connect.whichProvider': 'Kunci ini dari penyedia mana?',
|
||||
'assistant.connect.testing': 'Memeriksa koneksi',
|
||||
'assistant.connect.ok': 'Terhubung. Ayo mulai.',
|
||||
'assistant.connect.openAssistant': 'Buka asisten',
|
||||
'assistant.connect.badKey': 'Kunci ini tidak berfungsi.',
|
||||
'assistant.connect.unknownModel': 'Model ini tidak tersedia di sini.',
|
||||
'assistant.connect.getKey': 'Dapatkan kunci',
|
||||
'assistant.connect.hintAnthropic': 'Claude Max dan Team sudah termasuk kredit API.',
|
||||
'assistant.connect.hintOpenai': 'Buat kunci di dasbor OpenAI kamu.',
|
||||
'assistant.connect.hintGemini': 'AI Studio punya tingkat gratis dengan batas per menit dan per hari, di mana Google dapat memakai apa yang kamu kirim untuk meningkatkan produknya. Dengan penagihan aktif, batasnya lebih tinggi dan hal itu tidak berlaku.',
|
||||
'assistant.connect.hintOpenrouter': 'Masuk dengan OpenRouter, tanpa perlu menyalin kunci.',
|
||||
'assistant.connect.openrouter': 'Hubungkan OpenRouter',
|
||||
'assistant.connect.save': 'Hubungkan',
|
||||
'assistant.connect.model': 'Model', // same-as-en
|
||||
'assistant.connect.modelHint': 'Hanya model yang bisa memakai alat yang ditampilkan.',
|
||||
'assistant.connect.free': 'Tersedia tingkat gratis',
|
||||
'assistant.connect.paid': 'Bayar sesuai pemakaian',
|
||||
'assistant.model.recommended': 'Disarankan',
|
||||
'assistant.model.free': 'Gratis',
|
||||
'assistant.connect.connected': 'Terhubung ke {provider}',
|
||||
'assistant.connect.providedByAdmin': 'Disediakan oleh admin kamu',
|
||||
'assistant.connect.disconnect': 'Putuskan',
|
||||
'assistant.connect.disconnectConfirm': 'Putuskan {provider}? Obrolanmu tetap tersimpan.',
|
||||
'assistant.connect.signOut': 'Keluar',
|
||||
'assistant.connect.statusOk': 'Terhubung',
|
||||
'assistant.connect.statusNeedsReauth': 'Sesi masuk kedaluwarsa',
|
||||
'assistant.connect.statusSignedOut': 'Sudah keluar',
|
||||
'assistant.connect.statusError': 'Tidak dapat dijangkau',
|
||||
'assistant.connect.quota': '{used} dari {count} pesan terpakai hari ini',
|
||||
'assistant.connect.claudeLink': 'Pakai Claude? Hubungkan di Claude',
|
||||
'assistant.privacy.what': 'Apa saja yang dibagikan?',
|
||||
'assistant.privacy.banner': 'Pesanmu dan data perjalanan yang dibaca asisten dikirim ke {provider}.',
|
||||
'assistant.privacy.ok': 'Mengerti',
|
||||
'assistant.privacy.shared': 'Pesanmu dan detail perjalanan yang dibaca asisten, seperti tempat, hari, dan pemesanan.',
|
||||
'assistant.privacy.notShared': 'Bukan kata sandimu, file-mu, atau perjalananmu yang lain.',
|
||||
'assistant.privacy.provider': '{provider} menanganinya sesuai ketentuan privasinya sendiri.',
|
||||
'assistant.empty.title': 'Kita merencanakan apa?',
|
||||
'assistant.empty.readOnly':
|
||||
'Kamu hanya bisa melihat perjalanan ini, jadi aku bisa menjawab pertanyaan tapi tidak bisa mengubah apa pun.',
|
||||
'assistant.suggest.planFirstDays': 'Rencanakan hari-hari pertama untukku',
|
||||
'assistant.suggest.mustSee': 'Apa yang tidak boleh kulewatkan di sini?',
|
||||
'assistant.suggest.packingList': 'Buat daftar barang bawaan untuk perjalanan ini',
|
||||
'assistant.suggest.emptyDays': 'Hari mana yang masih kosong?',
|
||||
'assistant.suggest.rainPlan': 'Apa yang bisa kita lakukan kalau hujan?',
|
||||
'assistant.suggest.today': 'Ada acara apa hari ini?',
|
||||
'assistant.suggest.eatNearby': 'Cari tempat makan di dekat sini',
|
||||
'assistant.suggest.weather': 'Bagaimana cuacanya?',
|
||||
'assistant.suggest.openTodos': 'Apa yang masih belum selesai di daftar tugas?',
|
||||
'assistant.suggest.settleUp': 'Siapa berutang kepada siapa?',
|
||||
'assistant.suggest.recap': 'Rangkum perjalanan kita',
|
||||
'assistant.suggest.nearPlace': 'Ada apa lagi di dekat {place}?',
|
||||
'assistant.suggest.placeTips': 'Tips mengunjungi {place}',
|
||||
'assistant.suggest.planDay': 'Rencanakan {day} untukku',
|
||||
'assistant.suggest.dayTooFull': 'Apakah {day} terlalu padat?',
|
||||
'assistant.thread.scrollDown': 'Lompat ke pesan terbaru',
|
||||
'assistant.attach.button': 'Lampirkan pemesanan',
|
||||
'assistant.attach.drop': 'Lepaskan di sini, aku akan membaca pemesanannya',
|
||||
'assistant.attach.unsupported': 'Jenis file ini tidak bisa dibaca. Coba PDF, email, gambar, atau kartu wallet.',
|
||||
'assistant.attach.tooLarge': 'File ini lebih dari 10 MB.',
|
||||
'assistant.composer.placeholder': 'Tanyakan apa saja tentang {trip}',
|
||||
'assistant.composer.send': 'Kirim',
|
||||
'assistant.composer.stop': 'Berhenti',
|
||||
'assistant.composer.decideFirst': 'Terapkan atau buang perubahan di atas terlebih dahulu.',
|
||||
'assistant.context.ask': 'Tanya',
|
||||
'assistant.context.remove': 'Hapus konteks',
|
||||
'assistant.step.reading': 'Membaca rencanamu',
|
||||
'assistant.step.searching': 'Mencari tempat',
|
||||
'assistant.step.details': 'Mencari detail',
|
||||
'assistant.step.nearby': 'Mencari di sekitar',
|
||||
'assistant.step.categories': 'Memeriksa kategori',
|
||||
'assistant.step.weather': 'Memeriksa cuaca',
|
||||
'assistant.step.todos': 'Membaca daftar tugas',
|
||||
'assistant.step.costs': 'Memeriksa biaya',
|
||||
'assistant.step.help': 'Mencari di bantuan',
|
||||
'assistant.step.readingBooking': 'Membaca pemesanan',
|
||||
'assistant.step.working': 'Sedang dikerjakan',
|
||||
'assistant.step.collapsed': '{count} langkah',
|
||||
'assistant.change.title': 'Perubahan yang disarankan',
|
||||
'assistant.change.doneTitle': 'Selesai',
|
||||
'assistant.change.apply': 'Terapkan',
|
||||
'assistant.change.applyN': 'Terapkan {count}',
|
||||
'assistant.change.discard': 'Buang',
|
||||
'assistant.change.include': 'Sertakan perubahan ini',
|
||||
'assistant.change.applied': 'Diterapkan',
|
||||
'assistant.change.discarded': 'Dibuang',
|
||||
'assistant.change.failed': 'Tidak bisa menerapkan ini.',
|
||||
'assistant.change.applying': 'Menerapkan',
|
||||
'assistant.change.expired': 'Tidak tersedia lagi',
|
||||
'assistant.change.undo': 'Urungkan',
|
||||
'assistant.change.undone': 'Diurungkan',
|
||||
'assistant.change.undoStale': 'Sudah berubah sementara itu. Silakan sesuaikan secara manual.',
|
||||
'assistant.change.undoFailed': 'Tidak bisa mengurungkan ini.',
|
||||
'assistant.change.showOnMap': 'Tampilkan di peta',
|
||||
'assistant.change.empty': 'kosong',
|
||||
'assistant.change.pending': '{count} perubahan menunggumu',
|
||||
'assistant.change.verb.addPlace': 'Tambah tempat',
|
||||
'assistant.change.verb.addPlaceToDay': 'Tambah tempat ke hari',
|
||||
'assistant.change.verb.updatePlace': 'Ubah tempat',
|
||||
'assistant.change.verb.assignPlace': 'Jadwalkan tempat di suatu hari',
|
||||
'assistant.change.verb.moveAssignment': 'Pindahkan ke hari lain',
|
||||
'assistant.change.verb.removeFromDay': 'Hapus dari hari',
|
||||
'assistant.change.verb.addDayNote': 'Tambah catatan',
|
||||
'assistant.change.verb.updateDayNote': 'Ubah catatan',
|
||||
'assistant.change.verb.addPackingItem': 'Tambah ke daftar bawaan',
|
||||
'assistant.change.verb.updatePackingItem': 'Ubah barang bawaan',
|
||||
'assistant.change.verb.addTodo': 'Tambah tugas',
|
||||
'assistant.change.verb.addBudgetItem': 'Tambah pengeluaran',
|
||||
'assistant.change.verb.addReservation': 'Tambah pemesanan',
|
||||
'assistant.change.verb.importBooking': 'Tambah pemesanan dari file',
|
||||
'assistant.change.label.name': 'Nama',
|
||||
'assistant.change.label.description': 'Deskripsi',
|
||||
'assistant.change.label.address': 'Alamat',
|
||||
'assistant.change.label.coordinates': 'Lokasi di peta',
|
||||
'assistant.change.label.category': 'Kategori',
|
||||
'assistant.change.label.day': 'Hari',
|
||||
'assistant.change.label.time': 'Waktu',
|
||||
'assistant.change.label.endTime': 'Sampai',
|
||||
'assistant.change.label.notes': 'Catatan',
|
||||
'assistant.change.label.website': 'Situs web',
|
||||
'assistant.change.label.phone': 'Telepon',
|
||||
'assistant.change.label.email': 'Email', // same-as-en
|
||||
'assistant.change.label.openingHours': 'Jam buka',
|
||||
'assistant.change.label.price': 'Harga',
|
||||
'assistant.change.label.currency': 'Mata uang',
|
||||
'assistant.change.label.stopType': 'Jenis pemberhentian',
|
||||
'assistant.change.label.text': 'Teks',
|
||||
'assistant.change.label.icon': 'Ikon',
|
||||
'assistant.change.label.color': 'Warna',
|
||||
'assistant.change.label.position': 'Posisi',
|
||||
'assistant.change.label.quantity': 'Jumlah',
|
||||
'assistant.change.label.weight': 'Berat',
|
||||
'assistant.change.label.bag': 'Tas',
|
||||
'assistant.change.label.packed': 'Sudah dikemas',
|
||||
'assistant.change.label.dueDate': 'Tenggat',
|
||||
'assistant.change.label.priority': 'Prioritas',
|
||||
'assistant.change.label.assignee': 'Ditugaskan ke',
|
||||
'assistant.change.label.type': 'Jenis',
|
||||
'assistant.change.label.location': 'Lokasi',
|
||||
'assistant.change.label.link': 'Tautan',
|
||||
'assistant.change.label.confirmation': 'Nomor konfirmasi',
|
||||
'assistant.change.label.checkIn': 'Check-in', // same-as-en
|
||||
'assistant.change.label.checkOut': 'Check-out', // same-as-en
|
||||
'assistant.change.label.stay': 'Menginap',
|
||||
'assistant.change.label.members': 'Dibagi antara',
|
||||
'assistant.change.label.route': 'Rute',
|
||||
'assistant.history.open': 'Obrolan terbaru',
|
||||
'assistant.history.new': 'Obrolan baru',
|
||||
'assistant.history.empty': 'Belum ada obrolan.',
|
||||
'assistant.history.retention': 'Obrolan dihapus setelah {count} hari.',
|
||||
'assistant.history.forever': 'Obrolan disimpan sampai kamu menghapusnya.',
|
||||
'assistant.error.notConnected': 'Asisten belum terhubung.',
|
||||
'assistant.error.busy': 'Sudah berjalan di jendela lain.',
|
||||
'assistant.error.dailyLimit': 'Sudah cukup untuk hari ini. Sampai besok.',
|
||||
'assistant.error.rateLimited': 'Pelan-pelan sedikit. Coba lagi sebentar lagi.',
|
||||
'assistant.error.needsReauth': 'Sesi masuk {provider} kamu sudah kedaluwarsa.',
|
||||
'assistant.error.usageLimit': 'Kuota ChatGPT kamu sudah habis.',
|
||||
'assistant.error.notEligible': 'Akun ChatGPT ini tidak bisa dipakai di sini.',
|
||||
'assistant.error.invalidKey': 'Kunci {provider} kamu sudah tidak berfungsi.',
|
||||
'assistant.error.providerDown': '{provider} sedang tidak merespons.',
|
||||
'assistant.error.maxSteps': 'Itu banyak sekaligus. Mau aku lanjutkan?',
|
||||
'assistant.error.offline': 'Sedang offline. Asisten butuh internet.',
|
||||
'assistant.error.noBookingsFound': 'Tidak ada pemesanan yang ditemukan di file ini.',
|
||||
'assistant.error.importUnavailable': 'Membaca file pemesanan tidak tersedia di sini.',
|
||||
'assistant.error.generic': 'Terjadi kesalahan.',
|
||||
'assistant.errorAction.retry': 'Coba lagi',
|
||||
'assistant.errorAction.continueHere': 'Lanjutkan di sini',
|
||||
'assistant.errorAction.continue': 'Lanjutkan',
|
||||
'assistant.errorAction.signIn': 'Masuk lagi',
|
||||
'assistant.errorAction.viewUsage': 'Lihat pemakaian',
|
||||
'assistant.errorAction.otherProvider': 'Pilih penyedia lain',
|
||||
'assistant.errorAction.replaceKey': 'Ganti kunci',
|
||||
'assistant.errorAction.connectOwn': 'Hubungkan penyediamu sendiri',
|
||||
'assistant.admin.policy': 'Siapa yang membayar',
|
||||
'assistant.admin.policy.instance': 'Aku bayar untuk semua orang',
|
||||
'assistant.admin.policy.instanceOrUser': 'Aku bayar, milik sendiri boleh',
|
||||
'assistant.admin.policy.user': 'Semua orang memakai akunnya sendiri',
|
||||
'assistant.admin.policy.instanceHint':
|
||||
'Semua orang memakai koneksi di bawah dan tidak bisa menambahkan milik sendiri.',
|
||||
'assistant.admin.policy.instanceOrUserHint':
|
||||
'Semua orang memakai koneksi di bawah, kecuali mereka menghubungkan milik sendiri.',
|
||||
'assistant.admin.policy.userHint': 'Semua orang menghubungkan penyedianya sendiri. Tidak ada biaya untukmu.',
|
||||
'assistant.admin.connection': 'Koneksi untuk semua orang',
|
||||
'assistant.admin.connectionNone': 'Belum ada koneksi.',
|
||||
'assistant.admin.connectionRemove': 'Hapus koneksi',
|
||||
'assistant.admin.custom': 'Server sendiri',
|
||||
'assistant.admin.customHint': 'Server apa pun yang kompatibel dengan OpenAI, misalnya Ollama.',
|
||||
'assistant.admin.baseUrl': 'Alamat server',
|
||||
'assistant.admin.apiKeyOptional': 'Kunci API (opsional)',
|
||||
'assistant.admin.prefilled': 'Diambil dari Analisis AI.',
|
||||
'assistant.admin.dailyLimit': 'Pesan per orang per hari',
|
||||
'assistant.admin.dailyLimitHint': 'Hanya dihitung selama kamu yang membayar.',
|
||||
'assistant.admin.retention': 'Simpan obrolan',
|
||||
'assistant.admin.retentionDays': '{count} hari',
|
||||
'assistant.admin.retentionForever': 'Selamanya',
|
||||
'assistant.claude.title': 'Hubungkan dengan Claude',
|
||||
'assistant.claude.description': 'Pakai TREK langsung di dalam Claude: tambahkan sebagai konektor, masuk, selesai.',
|
||||
'assistant.claude.open': 'Buka di Claude',
|
||||
'assistant.claude.scan': 'Atau pindai dengan ponselmu',
|
||||
'assistant.claude.copyUrl': 'Salin alamat',
|
||||
'assistant.claude.qrAlt': 'Kode QR untuk menambahkan TREK di Claude',
|
||||
'assistant.claude.notPublic': 'Claude hanya bisa menjangkau TREK jika TREK tersedia lewat https di internet.',
|
||||
'assistant.mobile.tile': 'Tanya TREK',
|
||||
'assistant.mobile.tileHint': 'Rencanakan bersama asisten',
|
||||
'assistant.mobile.backToChat': 'Kembali ke obrolan',
|
||||
'assistant.step.nearbyAt': 'Mencari di sekitar {place}',
|
||||
'assistant.step.searchingFor': 'Mencari {query}',
|
||||
'assistant.step.weatherAt': 'Memeriksa cuaca di {place}',
|
||||
'assistant.change.timeline.show': 'Tampilkan jadwal',
|
||||
'assistant.change.timeline.hide': 'Sembunyikan jadwal',
|
||||
'assistant.change.timeline.new': 'Baru',
|
||||
'assistant.change.timeline.travel': '≈ {count} menit perjalanan',
|
||||
'assistant.change.timeline.gap': '{count} menit kosong',
|
||||
'assistant.change.timeline.overlap': 'Bertabrakan dengan {place}',
|
||||
'assistant.change.timeline.moreDays': '{count} hari lagi',
|
||||
'assistant.change.viewOnMap': 'Lihat di peta',
|
||||
'assistant.change.before': 'Sebelum',
|
||||
'assistant.change.after': 'Sesudah',
|
||||
'assistant.change.compare': 'Bandingkan di peta',
|
||||
'assistant.change.undoAll': 'Urungkan semua',
|
||||
'assistant.change.undoneAll': '{count} perubahan diurungkan',
|
||||
'assistant.change.undoAllPartial': '{done} diurungkan, {stale} sudah berubah sejak itu',
|
||||
'assistant.change.undoAllFailed': '{count} perubahan tidak bisa diurungkan',
|
||||
'assistant.place.strip': 'Tempat yang ditemukan',
|
||||
'assistant.place.addTo': 'Tambahkan ke {day}',
|
||||
'assistant.place.addToShort': '+ Hari {n}',
|
||||
'assistant.place.pickDay': 'Pilih hari',
|
||||
'assistant.place.suggested': 'Diusulkan',
|
||||
'assistant.place.inTrip': 'Sudah ada di perjalananmu',
|
||||
'assistant.place.showOnMap': 'Di peta',
|
||||
'assistant.place.openNow': 'Buka sekarang',
|
||||
'assistant.place.closed': 'Tutup',
|
||||
'assistant.place.fromPrev': '{distance} dari pemberhentian sebelumnya',
|
||||
'assistant.place.more': '{count} lagi',
|
||||
'assistant.mention.days': 'Hari',
|
||||
'assistant.mention.places': 'Tempat',
|
||||
'assistant.mention.bookings': 'Pemesanan',
|
||||
'assistant.mention.none': 'Tidak ada yang ditemukan',
|
||||
'assistant.mention.hint': 'Ketik @ untuk menyebut hari, tempat, dan pemesanan.',
|
||||
'assistant.attach.dropPlace': 'Lepaskan tempat di sini',
|
||||
'assistant.weather.strip': 'Cuaca untuk {count} hari',
|
||||
'assistant.weather.rain': '{pct}% hujan',
|
||||
'assistant.settings.followMap': 'Peta mengikuti asisten',
|
||||
'assistant.settings.followMapHint': 'Peta bergeser ke tempat asisten mencari dan mengusulkan tempat.',
|
||||
'assistant.error.decideFirst': 'Putuskan dulu usulan yang masih terbuka.',
|
||||
'assistant.error.providerLimit': 'Batas di {provider} tercapai. Coba lagi nanti.',
|
||||
'assistant.hint.title': 'Perlu dilihat',
|
||||
'assistant.hint.dismiss': 'Sembunyikan petunjuk ini',
|
||||
'assistant.hint.more': '{count} lagi',
|
||||
'assistant.hint.overlap': '{a} dan {b} bertabrakan pada {day}',
|
||||
'assistant.hint.overlap.prompt': 'Pada {day}, {a} dan {b} bertabrakan. Bagaimana kita mengaturnya?',
|
||||
'assistant.hint.closed': '{place} tutup pada {day}',
|
||||
'assistant.hint.closed.prompt': '{place} tutup pada {day}. Apa yang bisa kita lakukan sebagai gantinya?',
|
||||
'assistant.hint.opensLate': '{place} baru buka pukul {time} pada {day}',
|
||||
'assistant.hint.closesEarly': '{place} sudah tutup pukul {time} pada {day}',
|
||||
'assistant.hint.outsideHours.prompt':
|
||||
'Kunjungan kita ke {place} pada {day} tidak cocok dengan jam bukanya. Bagaimana sebaiknya kita pindahkan?',
|
||||
'assistant.hint.gap': '{day}: {count} jam kosong mulai {from}',
|
||||
'assistant.hint.gap.prompt': 'Tidak ada rencana pada {day} dari {from} sampai {to}. Apa yang cocok di sana?',
|
||||
'assistant.hint.longLeg': '{from} dan {to} berjarak {distance} garis lurus',
|
||||
'assistant.hint.longLeg.prompt': 'Apakah {from} ke {to} pada {day} terlalu jauh? Bagaimana cara terbaik ke sana?',
|
||||
'assistant.hint.tightLeg': 'Hanya {count} menit antara {from} dan {to}',
|
||||
'assistant.hint.tightLeg.prompt':
|
||||
'Cukupkah waktunya pada {day} untuk pergi dari {from} ke {to}? Apa yang perlu diubah?',
|
||||
'assistant.hint.noStay': 'Belum ada penginapan untuk {count} malam mulai {day}',
|
||||
'assistant.hint.noStay.prompt': 'Kita masih butuh penginapan mulai {day}. Apa saranmu?',
|
||||
'assistant.hint.unplacedBooking': '{title} belum ada di hari mana pun',
|
||||
'assistant.hint.unplacedBookingDay': '{title} sepertinya untuk {day}',
|
||||
'assistant.hint.unplacedBookings': '{count} pemesanan belum ada di hari mana pun',
|
||||
'assistant.hint.unplacedBooking.prompt': 'Pemesanan mana yang belum ada di hari mana pun, dan ke mana seharusnya?',
|
||||
'assistant.hint.menuOn': 'Tampilkan petunjuk',
|
||||
'assistant.hint.menuOff': 'Jangan tampilkan petunjuk',
|
||||
'assistant.launcher.withHints': 'Asisten, {count} petunjuk',
|
||||
'assistant.mobile.tileHints': '{count} hal untuk dicek di rencanamu',
|
||||
'assistant.share.button': 'Bagikan ke obrolan perjalanan',
|
||||
'assistant.share.title': 'Bagikan ke obrolan perjalanan',
|
||||
'assistant.share.who': 'Semua orang di perjalanan ini akan melihat pesan ini, ditandai berasal dari asisten.',
|
||||
'assistant.share.includeChanges': 'Sertakan perubahan yang diterapkan',
|
||||
'assistant.share.changesHeading': 'Diterapkan:',
|
||||
'assistant.share.truncated': 'Dipersingkat menjadi {count} karakter',
|
||||
'assistant.share.send': 'Bagikan',
|
||||
'assistant.share.done': 'Dibagikan ke obrolan perjalanan',
|
||||
'assistant.share.shared': 'Dibagikan',
|
||||
'assistant.share.failed': 'Gagal membagikan. Coba lagi sebentar lagi.',
|
||||
'assistant.share.chatOff': 'Obrolan perjalanan dimatikan.',
|
||||
};
|
||||
export default assistant;
|
||||
@@ -101,5 +101,7 @@ const collab: TranslationStrings = {
|
||||
'collab.chat.attachImages': 'Lampirkan gambar',
|
||||
'collab.chat.attachedImage': 'Gambar terlampir',
|
||||
'collab.chat.closeImage': 'Tutup gambar',
|
||||
'collab.chat.fromAssistant': 'dari asisten',
|
||||
'collab.chat.fromAssistantHint': '{name} membagikan jawaban asisten ini',
|
||||
};
|
||||
export default collab;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import admin from './admin';
|
||||
import airport from './airport';
|
||||
import assistant from './assistant';
|
||||
import atlas from './atlas';
|
||||
import backup from './backup';
|
||||
import budget from './budget';
|
||||
@@ -116,5 +117,6 @@ const locale = {
|
||||
...dawarich,
|
||||
...docsync,
|
||||
...ferratas,
|
||||
...assistant,
|
||||
};
|
||||
export default locale;
|
||||
|
||||
@@ -83,6 +83,7 @@ const oauth: TranslationStrings = {
|
||||
'oauth.authorize.loginButton': 'Sign in to TREK', // en-fallback
|
||||
'oauth.authorize.requestLabel': 'Authorization Request', // en-fallback
|
||||
'oauth.authorize.requestDescription': 'This application is requesting access to your TREK account.', // en-fallback
|
||||
'oauth.authorize.verifiedHost': 'Domain terverifikasi: {host}',
|
||||
'oauth.authorize.trustNote': 'Only grant access to applications you trust. Your data stays on your server.', // en-fallback
|
||||
'oauth.authorize.selectScope': 'Select at least one scope', // en-fallback
|
||||
'oauth.authorize.approveScopes': 'Setujui ({count} cakupan)',
|
||||
|
||||
@@ -642,6 +642,9 @@ const admin: TranslationStrings = {
|
||||
'admin.addons.catalog.llm_parsing.name': 'Analisi con IA',
|
||||
'admin.addons.catalog.llm_parsing.description':
|
||||
'Legge le prenotazioni che l’analizzatore integrato non riesce a interpretare, con un modello di IA a tua scelta',
|
||||
'admin.addons.catalog.ai_assistant.name': 'Assistente IA',
|
||||
'admin.addons.catalog.ai_assistant.description':
|
||||
'Una chat in ogni viaggio che risponde alle domande e propone modifiche che confermi tu',
|
||||
'admin.addons.llm.vision.auto': 'Automatico',
|
||||
'admin.addons.llm.vision.on': 'Sì',
|
||||
'admin.addons.llm.vision.off': 'No',
|
||||
|
||||
@@ -0,0 +1,346 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistant: TranslationStrings = {
|
||||
'assistant.launcher.tooltip': 'Assistente',
|
||||
'assistant.launcher.ctrlKey': 'Ctrl', // same-as-en
|
||||
'assistant.picker.title': 'Quale viaggio?',
|
||||
'assistant.picker.empty': 'Crea prima un viaggio, poi l’assistente ti aiuta a pianificarlo.',
|
||||
'assistant.picker.now': 'Adesso',
|
||||
'assistant.picker.next': 'Prossimo',
|
||||
'assistant.window.title': 'Assistente',
|
||||
'assistant.window.log': 'Conversazione con l’assistente',
|
||||
'assistant.window.menu': 'Altre opzioni',
|
||||
'assistant.window.settings': 'Impostazioni IA',
|
||||
'assistant.window.deleteThread': 'Elimina chat',
|
||||
'assistant.window.deleteConfirm': 'Eliminare questa chat? L’operazione non si può annullare.',
|
||||
'assistant.setup.question': 'Con cosa vuoi chattare?',
|
||||
'assistant.setup.beta': 'Beta', // same-as-en
|
||||
'assistant.setup.chatgptNote': 'La via ufficiale è una chiave API. L’accesso con ChatGPT è ancora in beta.',
|
||||
'assistant.setup.keySubtitle': 'Anthropic, OpenAI, Gemini o OpenRouter',
|
||||
'assistant.setup.adminOnly': 'Se ne occupa il tuo admin.',
|
||||
'assistant.setup.forEveryone': 'Configura per tutti',
|
||||
'assistant.connect.title': 'Assistente IA',
|
||||
'assistant.connect.description':
|
||||
'Chatta con un assistente che conosce il tuo viaggio e propone modifiche che confermi tu.',
|
||||
'assistant.connect.chatgpt': 'Accedi con ChatGPT',
|
||||
'assistant.connect.chatgptHint': 'Usa il tuo piano ChatGPT. Nessuna chiave necessaria.',
|
||||
'assistant.connect.chatgptStep1': 'Accedi a ChatGPT nella nuova scheda.',
|
||||
'assistant.connect.chatgptOpen': 'Apri l’accesso a ChatGPT',
|
||||
'assistant.connect.chatgptStep2':
|
||||
'Poi il browser mostrerà una pagina di errore. È normale. Copia l’indirizzo in alto e torna qui.',
|
||||
'assistant.connect.chatgptPaste': 'Incolla indirizzo',
|
||||
'assistant.connect.chatgptWrong': 'Non è l’indirizzo giusto. Inizia con 127.0.0.1.',
|
||||
'assistant.connect.chatgptExpired': 'Ci è voluto troppo. Accedi di nuovo.',
|
||||
'assistant.connect.chatgptOtherAccount': 'Questo è un altro account ChatGPT. Rimuovi prima la connessione.',
|
||||
'assistant.connect.key': 'Incolla una chiave API',
|
||||
'assistant.connect.keyPlaceholder': 'Incolla qui la tua chiave',
|
||||
'assistant.connect.detected': 'Rilevato: {provider}',
|
||||
'assistant.connect.whichProvider': 'Di quale fornitore è questa chiave?',
|
||||
'assistant.connect.testing': 'Verifica della connessione',
|
||||
'assistant.connect.ok': 'Connesso. Si parte.',
|
||||
'assistant.connect.openAssistant': 'Apri l’assistente',
|
||||
'assistant.connect.badKey': 'Questa chiave non funziona.',
|
||||
'assistant.connect.unknownModel': 'Questo modello non è disponibile qui.',
|
||||
'assistant.connect.getKey': 'Ottieni una chiave',
|
||||
'assistant.connect.hintAnthropic': 'Claude Max e Team includono crediti API.',
|
||||
'assistant.connect.hintOpenai': 'Crea una chiave nella dashboard di OpenAI.',
|
||||
'assistant.connect.hintGemini': 'AI Studio ha un livello gratuito con limiti al minuto e al giorno, in cui Google può usare ciò che invii per migliorare i suoi prodotti. Con la fatturazione attiva i limiti sono più alti e questo non vale.',
|
||||
'assistant.connect.hintOpenrouter': 'Accedi con OpenRouter, nessuna chiave da copiare.',
|
||||
'assistant.connect.openrouter': 'Collega OpenRouter',
|
||||
'assistant.connect.save': 'Collega',
|
||||
'assistant.connect.model': 'Modello',
|
||||
'assistant.connect.modelHint': 'Sono elencati solo i modelli in grado di usare strumenti.',
|
||||
'assistant.connect.free': 'Livello gratuito disponibile',
|
||||
'assistant.connect.paid': 'A consumo',
|
||||
'assistant.model.recommended': 'Consigliato',
|
||||
'assistant.model.free': 'Gratis',
|
||||
'assistant.connect.connected': 'Connesso a {provider}',
|
||||
'assistant.connect.providedByAdmin': 'Fornito dal tuo admin',
|
||||
'assistant.connect.disconnect': 'Disconnetti',
|
||||
'assistant.connect.disconnectConfirm': 'Disconnettere {provider}? Le tue chat restano.',
|
||||
'assistant.connect.signOut': 'Esci',
|
||||
'assistant.connect.statusOk': 'Connesso',
|
||||
'assistant.connect.statusNeedsReauth': 'Accesso scaduto',
|
||||
'assistant.connect.statusSignedOut': 'Disconnesso',
|
||||
'assistant.connect.statusError': 'Non raggiungibile',
|
||||
'assistant.connect.quota': '{used} di {count} messaggi usati oggi',
|
||||
'assistant.connect.quota.one': '{used} di {count} messaggio usato oggi',
|
||||
'assistant.connect.claudeLink': 'Usi Claude? Collegati in Claude',
|
||||
'assistant.privacy.what': 'Cosa viene condiviso?',
|
||||
'assistant.privacy.banner':
|
||||
'I tuoi messaggi e i dati del viaggio letti dall’assistente vengono inviati a {provider}.',
|
||||
'assistant.privacy.ok': 'Ho capito',
|
||||
'assistant.privacy.shared':
|
||||
'I tuoi messaggi e i dettagli del viaggio letti dall’assistente, come luoghi, giorni e prenotazioni.',
|
||||
'assistant.privacy.notShared': 'Non la tua password, i tuoi file né gli altri tuoi viaggi.',
|
||||
'assistant.privacy.provider': '{provider} li tratta secondo la propria informativa sulla privacy.',
|
||||
'assistant.empty.title': 'Cosa pianifichiamo?',
|
||||
'assistant.empty.readOnly':
|
||||
'Puoi solo visualizzare questo viaggio, quindi posso rispondere alle domande ma non modificare nulla.',
|
||||
'assistant.suggest.planFirstDays': 'Pianifica i primi giorni per me',
|
||||
'assistant.suggest.mustSee': 'Cosa non devo perdermi qui?',
|
||||
'assistant.suggest.packingList': 'Prepara una lista bagagli per questo viaggio',
|
||||
'assistant.suggest.emptyDays': 'Quali giorni sono ancora vuoti?',
|
||||
'assistant.suggest.rainPlan': 'Cosa possiamo fare se piove?',
|
||||
'assistant.suggest.today': 'Cosa c’è in programma oggi?',
|
||||
'assistant.suggest.eatNearby': 'Trova un posto per mangiare qui vicino',
|
||||
'assistant.suggest.weather': 'Che tempo farà?',
|
||||
'assistant.suggest.openTodos': 'Cosa resta da fare nella lista delle attività?',
|
||||
'assistant.suggest.settleUp': 'Chi deve soldi a chi?',
|
||||
'assistant.suggest.recap': 'Riassumi il nostro viaggio',
|
||||
'assistant.suggest.nearPlace': 'Cos’altro c’è vicino a {place}?',
|
||||
'assistant.suggest.placeTips': 'Consigli per visitare {place}',
|
||||
'assistant.suggest.planDay': 'Pianifica {day} per me',
|
||||
'assistant.suggest.dayTooFull': '{day} è troppo pieno?',
|
||||
'assistant.thread.scrollDown': 'Vai all’ultimo messaggio',
|
||||
'assistant.attach.button': 'Allega una prenotazione',
|
||||
'assistant.attach.drop': 'Rilascialo qui e leggo la prenotazione',
|
||||
'assistant.attach.unsupported': 'Non riesco a leggere questo tipo di file. Prova con un PDF, un’e-mail, un’immagine o un pass Wallet.',
|
||||
'assistant.attach.tooLarge': 'Questo file supera i 10 MB.',
|
||||
'assistant.composer.placeholder': 'Chiedi qualsiasi cosa su {trip}',
|
||||
'assistant.composer.send': 'Invia',
|
||||
'assistant.composer.stop': 'Ferma',
|
||||
'assistant.composer.decideFirst': 'Prima applica o scarta le modifiche qui sopra.',
|
||||
'assistant.context.ask': 'Chiedi',
|
||||
'assistant.context.remove': 'Rimuovi contesto',
|
||||
'assistant.step.reading': 'Leggo il tuo piano',
|
||||
'assistant.step.searching': 'Cerco luoghi',
|
||||
'assistant.step.details': 'Cerco i dettagli',
|
||||
'assistant.step.nearby': 'Cerco nei dintorni',
|
||||
'assistant.step.categories': 'Controllo le categorie',
|
||||
'assistant.step.weather': 'Controllo il meteo',
|
||||
'assistant.step.todos': 'Leggo la lista delle attività',
|
||||
'assistant.step.costs': 'Controllo i costi',
|
||||
'assistant.step.help': 'Cerco nella guida',
|
||||
'assistant.step.readingBooking': 'Leggo la prenotazione',
|
||||
'assistant.step.working': 'Ci sto lavorando',
|
||||
'assistant.step.collapsed': '{count} passaggi',
|
||||
'assistant.step.collapsed.one': '{count} passaggio',
|
||||
'assistant.change.title': 'Modifiche suggerite',
|
||||
'assistant.change.doneTitle': 'Fatto',
|
||||
'assistant.change.apply': 'Applica',
|
||||
'assistant.change.applyN': 'Applica {count}',
|
||||
'assistant.change.applyN.one': 'Applica {count}',
|
||||
'assistant.change.discard': 'Scarta',
|
||||
'assistant.change.include': 'Includi questa modifica',
|
||||
'assistant.change.applied': 'Applicata',
|
||||
'assistant.change.discarded': 'Scartata',
|
||||
'assistant.change.failed': 'Impossibile applicarla.',
|
||||
'assistant.change.applying': 'Applicazione in corso',
|
||||
'assistant.change.expired': 'Non più disponibile',
|
||||
'assistant.change.undo': 'Annulla',
|
||||
'assistant.change.undone': 'Annullata',
|
||||
'assistant.change.undoStale': 'Nel frattempo è cambiata. Sistemala a mano.',
|
||||
'assistant.change.undoFailed': 'Impossibile annullarla.',
|
||||
'assistant.change.showOnMap': 'Mostra sulla mappa',
|
||||
'assistant.change.empty': 'vuoto',
|
||||
'assistant.change.pending': '{count} modifiche ti aspettano',
|
||||
'assistant.change.pending.one': '{count} modifica ti aspetta',
|
||||
'assistant.change.verb.addPlace': 'Aggiungi luogo',
|
||||
'assistant.change.verb.addPlaceToDay': 'Aggiungi luogo al giorno',
|
||||
'assistant.change.verb.updatePlace': 'Modifica luogo',
|
||||
'assistant.change.verb.assignPlace': 'Pianifica luogo in un giorno',
|
||||
'assistant.change.verb.moveAssignment': 'Sposta in un altro giorno',
|
||||
'assistant.change.verb.removeFromDay': 'Togli dal giorno',
|
||||
'assistant.change.verb.addDayNote': 'Aggiungi nota',
|
||||
'assistant.change.verb.updateDayNote': 'Modifica nota',
|
||||
'assistant.change.verb.addPackingItem': 'Aggiungi alla lista bagagli',
|
||||
'assistant.change.verb.updatePackingItem': 'Modifica oggetto da portare',
|
||||
'assistant.change.verb.addTodo': 'Aggiungi attività',
|
||||
'assistant.change.verb.addBudgetItem': 'Aggiungi spesa',
|
||||
'assistant.change.verb.addReservation': 'Aggiungi prenotazione',
|
||||
'assistant.change.verb.importBooking': 'Aggiungi prenotazione da file',
|
||||
'assistant.change.label.name': 'Nome',
|
||||
'assistant.change.label.description': 'Descrizione',
|
||||
'assistant.change.label.address': 'Indirizzo',
|
||||
'assistant.change.label.coordinates': 'Posizione sulla mappa',
|
||||
'assistant.change.label.category': 'Categoria',
|
||||
'assistant.change.label.day': 'Giorno',
|
||||
'assistant.change.label.time': 'Ora',
|
||||
'assistant.change.label.endTime': 'Fino alle',
|
||||
'assistant.change.label.notes': 'Note',
|
||||
'assistant.change.label.website': 'Sito web',
|
||||
'assistant.change.label.phone': 'Telefono',
|
||||
'assistant.change.label.email': 'Email', // same-as-en
|
||||
'assistant.change.label.openingHours': 'Orari di apertura',
|
||||
'assistant.change.label.price': 'Prezzo',
|
||||
'assistant.change.label.currency': 'Valuta',
|
||||
'assistant.change.label.stopType': 'Tipo di tappa',
|
||||
'assistant.change.label.text': 'Testo',
|
||||
'assistant.change.label.icon': 'Icona',
|
||||
'assistant.change.label.color': 'Colore',
|
||||
'assistant.change.label.position': 'Posizione',
|
||||
'assistant.change.label.quantity': 'Quantità',
|
||||
'assistant.change.label.weight': 'Peso',
|
||||
'assistant.change.label.bag': 'Borsa',
|
||||
'assistant.change.label.packed': 'Messo in valigia',
|
||||
'assistant.change.label.dueDate': 'Scadenza',
|
||||
'assistant.change.label.priority': 'Priorità',
|
||||
'assistant.change.label.assignee': 'Assegnato a',
|
||||
'assistant.change.label.type': 'Tipo',
|
||||
'assistant.change.label.location': 'Luogo',
|
||||
'assistant.change.label.link': 'Link', // same-as-en
|
||||
'assistant.change.label.confirmation': 'Numero di conferma',
|
||||
'assistant.change.label.checkIn': 'Check-in', // same-as-en
|
||||
'assistant.change.label.checkOut': 'Check-out', // same-as-en
|
||||
'assistant.change.label.stay': 'Soggiorno',
|
||||
'assistant.change.label.members': 'Diviso tra',
|
||||
'assistant.change.label.route': 'Percorso',
|
||||
'assistant.history.open': 'Chat recenti',
|
||||
'assistant.history.new': 'Nuova chat',
|
||||
'assistant.history.empty': 'Ancora nessuna chat.',
|
||||
'assistant.history.retention': 'Le chat vengono eliminate dopo {count} giorni.',
|
||||
'assistant.history.retention.one': 'Le chat vengono eliminate dopo {count} giorno.',
|
||||
'assistant.history.forever': 'Le chat restano finché non le elimini.',
|
||||
'assistant.error.notConnected': 'L’assistente non è ancora collegato.',
|
||||
'assistant.error.busy': 'È già in esecuzione in un’altra finestra.',
|
||||
'assistant.error.dailyLimit': 'Per oggi è tutto. A domani.',
|
||||
'assistant.error.rateLimited': 'Un po’ più piano. Riprova tra un minuto.',
|
||||
'assistant.error.needsReauth': 'Il tuo accesso a {provider} è scaduto.',
|
||||
'assistant.error.usageLimit': 'Hai esaurito il tuo utilizzo di ChatGPT.',
|
||||
'assistant.error.notEligible': 'Questo account ChatGPT non si può usare qui.',
|
||||
'assistant.error.invalidKey': 'La tua chiave {provider} non funziona più.',
|
||||
'assistant.error.providerDown': '{provider} non risponde in questo momento.',
|
||||
'assistant.error.maxSteps': 'Era tanto tutto insieme. Continuo?',
|
||||
'assistant.error.offline': 'Sei offline. L’assistente ha bisogno di internet.',
|
||||
'assistant.error.noBookingsFound': 'Nessuna prenotazione trovata in questo file.',
|
||||
'assistant.error.importUnavailable': 'La lettura dei file di prenotazione non è disponibile qui.',
|
||||
'assistant.error.generic': 'Qualcosa è andato storto.',
|
||||
'assistant.errorAction.retry': 'Riprova',
|
||||
'assistant.errorAction.continueHere': 'Continua qui',
|
||||
'assistant.errorAction.continue': 'Continua',
|
||||
'assistant.errorAction.signIn': 'Accedi di nuovo',
|
||||
'assistant.errorAction.viewUsage': 'Vedi utilizzo',
|
||||
'assistant.errorAction.otherProvider': 'Scegli un altro fornitore',
|
||||
'assistant.errorAction.replaceKey': 'Sostituisci chiave',
|
||||
'assistant.errorAction.connectOwn': 'Collega il tuo fornitore',
|
||||
'assistant.admin.policy': 'Chi paga',
|
||||
'assistant.admin.policy.instance': 'Pago io per tutti',
|
||||
'assistant.admin.policy.instanceOrUser': 'Pago io, propri ammessi',
|
||||
'assistant.admin.policy.user': 'Ognuno usa il proprio account',
|
||||
'assistant.admin.policy.instanceHint': 'Tutti usano la connessione qui sotto e non possono aggiungerne una propria.',
|
||||
'assistant.admin.policy.instanceOrUserHint':
|
||||
'Tutti usano la connessione qui sotto, a meno che non ne colleghino una propria.',
|
||||
'assistant.admin.policy.userHint': 'Ognuno collega il proprio fornitore. A te non viene addebitato nulla.',
|
||||
'assistant.admin.connection': 'Connessione per tutti',
|
||||
'assistant.admin.connectionNone': 'Ancora nessuna connessione.',
|
||||
'assistant.admin.connectionRemove': 'Rimuovi connessione',
|
||||
'assistant.admin.custom': 'Server proprio',
|
||||
'assistant.admin.customHint': 'Qualsiasi server compatibile con OpenAI, per esempio Ollama.',
|
||||
'assistant.admin.baseUrl': 'Indirizzo del server',
|
||||
'assistant.admin.apiKeyOptional': 'Chiave API (facoltativa)',
|
||||
'assistant.admin.prefilled': 'Ripreso da Analisi con IA.',
|
||||
'assistant.admin.dailyLimit': 'Messaggi per persona al giorno',
|
||||
'assistant.admin.dailyLimitHint': 'Conta solo finché paghi tu.',
|
||||
'assistant.admin.retention': 'Conserva le chat',
|
||||
'assistant.admin.retentionDays': '{count} giorni',
|
||||
'assistant.admin.retentionDays.one': '{count} giorno',
|
||||
'assistant.admin.retentionForever': 'Per sempre',
|
||||
'assistant.claude.title': 'Collega con Claude',
|
||||
'assistant.claude.description': 'Usa TREK direttamente in Claude: aggiungilo come connettore, accedi, fatto.',
|
||||
'assistant.claude.open': 'Apri in Claude',
|
||||
'assistant.claude.scan': 'Oppure scansionalo con il telefono',
|
||||
'assistant.claude.copyUrl': 'Copia indirizzo',
|
||||
'assistant.claude.qrAlt': 'Codice QR per aggiungere TREK in Claude',
|
||||
'assistant.claude.notPublic': 'Claude può raggiungere TREK solo se è disponibile via https su internet.',
|
||||
'assistant.mobile.tile': 'Chiedi a TREK',
|
||||
'assistant.mobile.tileHint': 'Pianifica con l’assistente',
|
||||
'assistant.mobile.backToChat': 'Torna alla chat',
|
||||
'assistant.step.nearbyAt': 'Cerco vicino a {place}',
|
||||
'assistant.step.searchingFor': 'Cerco {query}',
|
||||
'assistant.step.weatherAt': 'Controllo il meteo a {place}',
|
||||
'assistant.change.timeline.show': 'Mostra programma',
|
||||
'assistant.change.timeline.hide': 'Nascondi programma',
|
||||
'assistant.change.timeline.new': 'Nuovo',
|
||||
'assistant.change.timeline.travel': '≈ {count} min di tragitto',
|
||||
'assistant.change.timeline.travel.one': '≈ {count} min di tragitto',
|
||||
'assistant.change.timeline.gap': '{count} min liberi',
|
||||
'assistant.change.timeline.gap.one': '{count} min libero',
|
||||
'assistant.change.timeline.overlap': 'Si sovrappone a {place}',
|
||||
'assistant.change.timeline.moreDays': 'Altri {count} giorni',
|
||||
'assistant.change.timeline.moreDays.one': 'Altro {count} giorno',
|
||||
'assistant.change.viewOnMap': 'Vedi sulla mappa',
|
||||
'assistant.change.before': 'Prima',
|
||||
'assistant.change.after': 'Dopo',
|
||||
'assistant.change.compare': 'Confronta sulla mappa',
|
||||
'assistant.change.undoAll': 'Annulla tutto',
|
||||
'assistant.change.undoneAll': '{count} modifiche annullate',
|
||||
'assistant.change.undoneAll.one': '{count} modifica annullata',
|
||||
'assistant.change.undoAllPartial': 'Annullate: {done}; cambiate nel frattempo: {stale}',
|
||||
'assistant.change.undoAllFailed': 'Non è stato possibile annullare {count} modifiche',
|
||||
'assistant.change.undoAllFailed.one': 'Non è stato possibile annullare {count} modifica',
|
||||
'assistant.place.strip': 'Luoghi trovati',
|
||||
'assistant.place.addTo': 'Aggiungi a {day}',
|
||||
'assistant.place.addToShort': '+ Giorno {n}',
|
||||
'assistant.place.pickDay': 'Scegli un giorno',
|
||||
'assistant.place.suggested': 'Proposto',
|
||||
'assistant.place.inTrip': 'Già nel tuo viaggio',
|
||||
'assistant.place.showOnMap': 'Sulla mappa',
|
||||
'assistant.place.openNow': 'Aperto ora',
|
||||
'assistant.place.closed': 'Chiuso',
|
||||
'assistant.place.fromPrev': 'A {distance} dalla tappa precedente',
|
||||
'assistant.place.more': 'Altri {count}',
|
||||
'assistant.place.more.one': 'Altro {count}',
|
||||
'assistant.mention.days': 'Giorni',
|
||||
'assistant.mention.places': 'Luoghi',
|
||||
'assistant.mention.bookings': 'Prenotazioni',
|
||||
'assistant.mention.none': 'Nessun risultato',
|
||||
'assistant.mention.hint': 'Scrivi @ per citare giorni, luoghi e prenotazioni.',
|
||||
'assistant.attach.dropPlace': 'Rilascia qui il luogo',
|
||||
'assistant.weather.strip': 'Meteo per {count} giorni',
|
||||
'assistant.weather.strip.one': 'Meteo per {count} giorno',
|
||||
'assistant.weather.rain': '{pct}% di pioggia',
|
||||
'assistant.settings.followMap': 'La mappa segue l’assistente',
|
||||
'assistant.settings.followMapHint': 'La mappa si sposta dove l’assistente cerca e propone luoghi.',
|
||||
'assistant.error.decideFirst': 'Prima decidi sulle proposte in sospeso.',
|
||||
'assistant.error.providerLimit': 'Limite raggiunto su {provider}. Riprova più tardi.',
|
||||
'assistant.hint.title': 'Da controllare',
|
||||
'assistant.hint.dismiss': 'Nascondi questo avviso',
|
||||
'assistant.hint.more': 'Altri {count}',
|
||||
'assistant.hint.more.one': 'Altro {count}',
|
||||
'assistant.hint.overlap': '{a} e {b} si sovrappongono il {day}',
|
||||
'assistant.hint.overlap.prompt': 'Il {day} {a} e {b} si sovrappongono. Come risolviamo?',
|
||||
'assistant.hint.closed': '{place} è chiuso il {day}',
|
||||
'assistant.hint.closed.prompt': '{place} è chiuso il {day}. Cosa facciamo al suo posto?',
|
||||
'assistant.hint.opensLate': '{place} apre solo alle {time} il {day}',
|
||||
'assistant.hint.closesEarly': '{place} chiude già alle {time} il {day}',
|
||||
'assistant.hint.outsideHours.prompt':
|
||||
'La visita a {place} il {day} non rientra negli orari di apertura. Come la spostiamo al meglio?',
|
||||
'assistant.hint.gap': '{day}: {count} ore libere dalle {from}',
|
||||
'assistant.hint.gap.one': '{day}: {count} ora libera dalle {from}',
|
||||
'assistant.hint.gap.prompt': 'Il {day} non c’è niente in programma dalle {from} alle {to}. Cosa ci starebbe bene?',
|
||||
'assistant.hint.longLeg': '{from} e {to} distano {distance} in linea d’aria',
|
||||
'assistant.hint.longLeg.prompt':
|
||||
'È troppo lontano andare da {from} a {to} il {day}? Qual è il modo migliore per arrivarci?',
|
||||
'assistant.hint.tightLeg': 'Solo {count} minuti tra {from} e {to}',
|
||||
'assistant.hint.tightLeg.one': 'Solo {count} minuto tra {from} e {to}',
|
||||
'assistant.hint.tightLeg.prompt':
|
||||
'Il {day} c’è abbastanza tempo per andare da {from} a {to}? Cosa dovremmo cambiare?',
|
||||
'assistant.hint.noStay': 'Ancora nessun alloggio per {count} notti dal {day}',
|
||||
'assistant.hint.noStay.one': 'Ancora nessun alloggio per {count} notte dal {day}',
|
||||
'assistant.hint.noStay.prompt': 'Ci manca ancora un alloggio dal {day}. Cosa proponi?',
|
||||
'assistant.hint.unplacedBooking': '{title} non è in nessun giorno',
|
||||
'assistant.hint.unplacedBookingDay': '{title} probabilmente va il {day}',
|
||||
'assistant.hint.unplacedBookings': '{count} prenotazioni non sono in nessun giorno',
|
||||
'assistant.hint.unplacedBookings.one': '{count} prenotazione non è in nessun giorno',
|
||||
'assistant.hint.unplacedBooking.prompt': 'Quali prenotazioni non sono in nessun giorno, e dove vanno?',
|
||||
'assistant.hint.menuOn': 'Mostra avvisi',
|
||||
'assistant.hint.menuOff': 'Non mostrare avvisi',
|
||||
'assistant.launcher.withHints': 'Assistente, {count} avvisi',
|
||||
'assistant.launcher.withHints.one': 'Assistente, {count} avviso',
|
||||
'assistant.mobile.tileHints': '{count} cose da controllare nel tuo piano',
|
||||
'assistant.mobile.tileHints.one': '{count} cosa da controllare nel tuo piano',
|
||||
'assistant.share.button': 'Condividi nella chat del viaggio',
|
||||
'assistant.share.title': 'Condividi nella chat del viaggio',
|
||||
'assistant.share.who': 'Tutti nel viaggio vedranno questo messaggio, segnato come dell’assistente.',
|
||||
'assistant.share.includeChanges': 'Aggiungi le modifiche applicate',
|
||||
'assistant.share.changesHeading': 'Applicato:',
|
||||
'assistant.share.truncated': 'Accorciato a {count} caratteri',
|
||||
'assistant.share.truncated.one': 'Accorciato a {count} carattere',
|
||||
'assistant.share.send': 'Condividi',
|
||||
'assistant.share.done': 'Condiviso nella chat del viaggio',
|
||||
'assistant.share.shared': 'Condiviso',
|
||||
'assistant.share.failed': 'Condivisione non riuscita. Riprova tra un attimo.',
|
||||
'assistant.share.chatOff': 'La chat del viaggio è disattivata.',
|
||||
};
|
||||
export default assistant;
|
||||
@@ -106,5 +106,7 @@ const collab: TranslationStrings = {
|
||||
'collab.chat.attachImages': 'Allega immagini',
|
||||
'collab.chat.attachedImage': 'Immagine allegata',
|
||||
'collab.chat.closeImage': 'Chiudi immagine',
|
||||
'collab.chat.fromAssistant': 'dall’assistente',
|
||||
'collab.chat.fromAssistantHint': '{name} ha condiviso questa risposta dell’assistente',
|
||||
};
|
||||
export default collab;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import admin from './admin';
|
||||
import airport from './airport';
|
||||
import assistant from './assistant';
|
||||
import atlas from './atlas';
|
||||
import backup from './backup';
|
||||
import budget from './budget';
|
||||
@@ -116,5 +117,6 @@ const locale = {
|
||||
...dawarich,
|
||||
...docsync,
|
||||
...ferratas,
|
||||
...assistant,
|
||||
};
|
||||
export default locale;
|
||||
|
||||
@@ -84,6 +84,7 @@ const oauth: TranslationStrings = {
|
||||
'oauth.authorize.loginButton': 'Sign in to TREK', // en-fallback
|
||||
'oauth.authorize.requestLabel': 'Authorization Request', // en-fallback
|
||||
'oauth.authorize.requestDescription': 'This application is requesting access to your TREK account.', // en-fallback
|
||||
'oauth.authorize.verifiedHost': 'Dominio verificato: {host}',
|
||||
'oauth.authorize.trustNote': 'Only grant access to applications you trust. Your data stays on your server.', // en-fallback
|
||||
'oauth.authorize.selectScope': 'Select at least one scope', // en-fallback
|
||||
'oauth.authorize.approveScopes': 'Approva ({count} ambiti)',
|
||||
|
||||
@@ -651,6 +651,9 @@ const admin: TranslationStrings = {
|
||||
'各自が接続した Dawarich インスタンスから、滞在と記録されたルートを読み取ります',
|
||||
'admin.addons.catalog.llm_parsing.name': 'AI 解析',
|
||||
'admin.addons.catalog.llm_parsing.description': '内蔵パーサーが読めない予約を、選んだ AI モデルで読み取ります',
|
||||
'admin.addons.catalog.ai_assistant.name': 'AI アシスタント',
|
||||
'admin.addons.catalog.ai_assistant.description':
|
||||
'旅行ごとに質問に答え、変更を提案するチャット。変更はあなたが確認してから反映されます',
|
||||
'admin.addons.llm.vision.auto': '自動',
|
||||
'admin.addons.llm.vision.on': 'はい',
|
||||
'admin.addons.llm.vision.off': 'いいえ',
|
||||
|
||||
@@ -0,0 +1,320 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistant: TranslationStrings = {
|
||||
'assistant.launcher.tooltip': 'アシスタント',
|
||||
'assistant.launcher.ctrlKey': 'Ctrl',
|
||||
'assistant.picker.title': 'どの旅行ですか?',
|
||||
'assistant.picker.empty': 'まず旅行を作成してください。その後、アシスタントが計画をお手伝いします。',
|
||||
'assistant.picker.now': '現在',
|
||||
'assistant.picker.next': '次',
|
||||
'assistant.window.title': 'アシスタント',
|
||||
'assistant.window.log': 'アシスタントとの会話',
|
||||
'assistant.window.menu': 'その他のオプション',
|
||||
'assistant.window.settings': 'AI 設定',
|
||||
'assistant.window.deleteThread': 'チャットを削除',
|
||||
'assistant.window.deleteConfirm': 'このチャットを削除しますか?元に戻すことはできません。',
|
||||
'assistant.setup.question': '何とチャットしますか?',
|
||||
'assistant.setup.beta': 'ベータ',
|
||||
'assistant.setup.chatgptNote': '公式の方法は API キーです。ChatGPT でのサインインはまだベータ版です。',
|
||||
'assistant.setup.keySubtitle': 'Anthropic、OpenAI、Gemini または OpenRouter',
|
||||
'assistant.setup.adminOnly': 'これは管理者が設定します。',
|
||||
'assistant.setup.forEveryone': '全員向けに設定',
|
||||
'assistant.connect.title': 'AI アシスタント',
|
||||
'assistant.connect.description':
|
||||
'旅行の内容を把握し、確認してから反映する変更を提案してくれるアシスタントとチャットできます。',
|
||||
'assistant.connect.chatgpt': 'ChatGPT でサインイン',
|
||||
'assistant.connect.chatgptHint': 'ご利用中の ChatGPT プランを使います。キーは不要です。',
|
||||
'assistant.connect.chatgptStep1': '新しいタブで ChatGPT にサインインしてください。',
|
||||
'assistant.connect.chatgptOpen': 'ChatGPT のサインインを開く',
|
||||
'assistant.connect.chatgptStep2':
|
||||
'その後、ブラウザにエラーページが表示されますが、問題ありません。上部のアドレスをコピーして戻ってきてください。',
|
||||
'assistant.connect.chatgptPaste': 'アドレスを貼り付け',
|
||||
'assistant.connect.chatgptWrong': '正しいアドレスではありません。127.0.0.1 で始まるアドレスです。',
|
||||
'assistant.connect.chatgptExpired': '時間がかかりすぎました。もう一度サインインしてください。',
|
||||
'assistant.connect.chatgptOtherAccount': '別の ChatGPT アカウントです。先に接続を削除してください。',
|
||||
'assistant.connect.key': 'API キーを貼り付け',
|
||||
'assistant.connect.keyPlaceholder': 'ここにキーを貼り付け',
|
||||
'assistant.connect.detected': '検出: {provider}',
|
||||
'assistant.connect.whichProvider': 'このキーはどのプロバイダーのものですか?',
|
||||
'assistant.connect.testing': '接続を確認中',
|
||||
'assistant.connect.ok': '接続しました。始めましょう。',
|
||||
'assistant.connect.openAssistant': 'アシスタントを開く',
|
||||
'assistant.connect.badKey': 'このキーは使えません。',
|
||||
'assistant.connect.unknownModel': 'このモデルはここでは提供されていません。',
|
||||
'assistant.connect.getKey': 'キーを取得',
|
||||
'assistant.connect.hintAnthropic': 'Claude Max と Team には API クレジットが含まれています。',
|
||||
'assistant.connect.hintOpenai': 'OpenAI のダッシュボードでキーを作成してください。',
|
||||
'assistant.connect.hintGemini': 'AI Studio には 1 分あたりと 1 日あたりの制限がある無料枠があり、そこでは送信内容が Google の製品改善に使われることがあります。課金を有効にすると制限が上がり、この扱いはなくなります。',
|
||||
'assistant.connect.hintOpenrouter': 'OpenRouter でサインインするだけで、キーのコピーは不要です。',
|
||||
'assistant.connect.openrouter': 'OpenRouter に接続',
|
||||
'assistant.connect.save': '接続',
|
||||
'assistant.connect.model': 'モデル',
|
||||
'assistant.connect.modelHint': 'ツールを使えるモデルだけが表示されます。',
|
||||
'assistant.connect.free': '無料枠あり',
|
||||
'assistant.connect.paid': '従量課金',
|
||||
'assistant.model.recommended': 'おすすめ',
|
||||
'assistant.model.free': '無料',
|
||||
'assistant.connect.connected': '{provider} に接続済み',
|
||||
'assistant.connect.providedByAdmin': '管理者が提供',
|
||||
'assistant.connect.disconnect': '接続を解除',
|
||||
'assistant.connect.disconnectConfirm': '{provider} との接続を解除しますか?チャットは残ります。',
|
||||
'assistant.connect.signOut': 'サインアウト',
|
||||
'assistant.connect.statusOk': '接続済み',
|
||||
'assistant.connect.statusNeedsReauth': 'サインインの期限切れ',
|
||||
'assistant.connect.statusSignedOut': 'サインアウト済み',
|
||||
'assistant.connect.statusError': '接続できません',
|
||||
'assistant.connect.quota': '本日 {count} 件中 {used} 件のメッセージを使用',
|
||||
'assistant.connect.claudeLink': 'Claude をお使いですか?Claude で接続',
|
||||
'assistant.privacy.what': '何が共有されますか?',
|
||||
'assistant.privacy.banner': 'メッセージと、アシスタントが読み取る旅行データは {provider} に送信されます。',
|
||||
'assistant.privacy.ok': 'わかりました',
|
||||
'assistant.privacy.shared': 'メッセージと、アシスタントが読み取る場所、日程、予約などの旅行の詳細。',
|
||||
'assistant.privacy.notShared': 'パスワード、ファイル、ほかの旅行は共有されません。',
|
||||
'assistant.privacy.provider': '{provider} は独自のプライバシー規約に基づいてこれを取り扱います。',
|
||||
'assistant.empty.title': '何を計画しましょうか?',
|
||||
'assistant.empty.readOnly': 'この旅行は閲覧のみなので、質問には答えられますが、変更はできません。',
|
||||
'assistant.suggest.planFirstDays': '最初の数日を計画して',
|
||||
'assistant.suggest.mustSee': 'ここで見逃せないものは?',
|
||||
'assistant.suggest.packingList': 'この旅行の持ち物リストを作って',
|
||||
'assistant.suggest.emptyDays': 'まだ予定のない日は?',
|
||||
'assistant.suggest.rainPlan': '雨の日は何ができる?',
|
||||
'assistant.suggest.today': '今日の予定は?',
|
||||
'assistant.suggest.eatNearby': '近くで食事できる場所を探して',
|
||||
'assistant.suggest.weather': '天気はどう?',
|
||||
'assistant.suggest.openTodos': 'ToDo リストで残っているものは?',
|
||||
'assistant.suggest.settleUp': '誰が誰にいくら払う?',
|
||||
'assistant.suggest.recap': '旅行をまとめて',
|
||||
'assistant.suggest.nearPlace': '{place} の近くにほかに何がある?',
|
||||
'assistant.suggest.placeTips': '{place} を訪れるコツ',
|
||||
'assistant.suggest.planDay': '{day} を計画して',
|
||||
'assistant.suggest.dayTooFull': '{day} は詰め込みすぎ?',
|
||||
'assistant.thread.scrollDown': '最新のメッセージへ移動',
|
||||
'assistant.attach.button': '予約を添付',
|
||||
'assistant.attach.drop': 'ここにドロップすると予約を読み取ります',
|
||||
'assistant.attach.unsupported': 'このファイル形式は読み取れません。PDF、メール、画像、ウォレットパスをお試しください。',
|
||||
'assistant.attach.tooLarge': 'このファイルは 10 MB を超えています。',
|
||||
'assistant.composer.placeholder': '{trip} について何でも質問',
|
||||
'assistant.composer.send': '送信',
|
||||
'assistant.composer.stop': '停止',
|
||||
'assistant.composer.decideFirst': '先に上の変更を適用するか破棄してください。',
|
||||
'assistant.context.ask': '質問',
|
||||
'assistant.context.remove': 'コンテキストを削除',
|
||||
'assistant.step.reading': '計画を読み込み中',
|
||||
'assistant.step.searching': '場所を検索中',
|
||||
'assistant.step.details': '詳細を確認中',
|
||||
'assistant.step.nearby': '周辺を検索中',
|
||||
'assistant.step.categories': 'カテゴリを確認中',
|
||||
'assistant.step.weather': '天気を確認中',
|
||||
'assistant.step.todos': 'ToDo リストを読み込み中',
|
||||
'assistant.step.costs': '費用を確認中',
|
||||
'assistant.step.help': 'ヘルプを検索中',
|
||||
'assistant.step.readingBooking': '予約を読み込み中',
|
||||
'assistant.step.working': '処理中',
|
||||
'assistant.step.collapsed': '{count} ステップ',
|
||||
'assistant.change.title': '提案された変更',
|
||||
'assistant.change.doneTitle': '完了',
|
||||
'assistant.change.apply': '適用',
|
||||
'assistant.change.applyN': '{count} 件を適用',
|
||||
'assistant.change.discard': '破棄',
|
||||
'assistant.change.include': 'この変更を含める',
|
||||
'assistant.change.applied': '適用済み',
|
||||
'assistant.change.discarded': '破棄済み',
|
||||
'assistant.change.failed': '適用できませんでした。',
|
||||
'assistant.change.applying': '適用中',
|
||||
'assistant.change.expired': '利用できなくなりました',
|
||||
'assistant.change.undo': '元に戻す',
|
||||
'assistant.change.undone': '元に戻しました',
|
||||
'assistant.change.undoStale': 'その間に変更されています。手動で調整してください。',
|
||||
'assistant.change.undoFailed': '元に戻せませんでした。',
|
||||
'assistant.change.showOnMap': '地図に表示',
|
||||
'assistant.change.empty': '空',
|
||||
'assistant.change.pending': '{count} 件の変更が確認待ちです',
|
||||
'assistant.change.verb.addPlace': '場所を追加',
|
||||
'assistant.change.verb.addPlaceToDay': '日程に場所を追加',
|
||||
'assistant.change.verb.updatePlace': '場所を変更',
|
||||
'assistant.change.verb.assignPlace': '場所を日程に割り当て',
|
||||
'assistant.change.verb.moveAssignment': '別の日に移動',
|
||||
'assistant.change.verb.removeFromDay': 'この日から外す',
|
||||
'assistant.change.verb.addDayNote': 'メモを追加',
|
||||
'assistant.change.verb.updateDayNote': 'メモを変更',
|
||||
'assistant.change.verb.addPackingItem': '持ち物リストに追加',
|
||||
'assistant.change.verb.updatePackingItem': '持ち物を変更',
|
||||
'assistant.change.verb.addTodo': 'ToDo を追加',
|
||||
'assistant.change.verb.addBudgetItem': '支出を追加',
|
||||
'assistant.change.verb.addReservation': '予約を追加',
|
||||
'assistant.change.verb.importBooking': 'ファイルから予約を追加',
|
||||
'assistant.change.label.name': '名前',
|
||||
'assistant.change.label.description': '説明',
|
||||
'assistant.change.label.address': '住所',
|
||||
'assistant.change.label.coordinates': '地図上の位置',
|
||||
'assistant.change.label.category': 'カテゴリ',
|
||||
'assistant.change.label.day': '日',
|
||||
'assistant.change.label.time': '時刻',
|
||||
'assistant.change.label.endTime': '終了',
|
||||
'assistant.change.label.notes': 'メモ',
|
||||
'assistant.change.label.website': 'ウェブサイト',
|
||||
'assistant.change.label.phone': '電話',
|
||||
'assistant.change.label.email': 'メール',
|
||||
'assistant.change.label.openingHours': '営業時間',
|
||||
'assistant.change.label.price': '料金',
|
||||
'assistant.change.label.currency': '通貨',
|
||||
'assistant.change.label.stopType': '立ち寄りの種類',
|
||||
'assistant.change.label.text': 'テキスト',
|
||||
'assistant.change.label.icon': 'アイコン',
|
||||
'assistant.change.label.color': '色',
|
||||
'assistant.change.label.position': '位置',
|
||||
'assistant.change.label.quantity': '数量',
|
||||
'assistant.change.label.weight': '重さ',
|
||||
'assistant.change.label.bag': 'バッグ',
|
||||
'assistant.change.label.packed': '荷造り済み',
|
||||
'assistant.change.label.dueDate': '期限',
|
||||
'assistant.change.label.priority': '優先度',
|
||||
'assistant.change.label.assignee': '担当者',
|
||||
'assistant.change.label.type': '種類',
|
||||
'assistant.change.label.location': '場所',
|
||||
'assistant.change.label.link': 'リンク',
|
||||
'assistant.change.label.confirmation': '確認番号',
|
||||
'assistant.change.label.checkIn': 'チェックイン',
|
||||
'assistant.change.label.checkOut': 'チェックアウト',
|
||||
'assistant.change.label.stay': '宿泊',
|
||||
'assistant.change.label.members': '割り勘の相手',
|
||||
'assistant.change.label.route': '区間',
|
||||
'assistant.history.open': '最近のチャット',
|
||||
'assistant.history.new': '新しいチャット',
|
||||
'assistant.history.empty': 'まだチャットはありません。',
|
||||
'assistant.history.retention': 'チャットは {count} 日後に削除されます。',
|
||||
'assistant.history.forever': 'チャットは削除するまで保存されます。',
|
||||
'assistant.error.notConnected': 'アシスタントはまだ接続されていません。',
|
||||
'assistant.error.busy': '別のウィンドウですでに実行中です。',
|
||||
'assistant.error.dailyLimit': '今日はここまでです。また明日。',
|
||||
'assistant.error.rateLimited': '少しペースを落としてください。1 分後にもう一度お試しください。',
|
||||
'assistant.error.needsReauth': '{provider} のサインインの期限が切れました。',
|
||||
'assistant.error.usageLimit': 'ChatGPT の利用枠を使い切りました。',
|
||||
'assistant.error.notEligible': 'この ChatGPT アカウントはここでは使えません。',
|
||||
'assistant.error.invalidKey': '{provider} のキーが使えなくなりました。',
|
||||
'assistant.error.providerDown': '{provider} が現在応答していません。',
|
||||
'assistant.error.maxSteps': '一度にたくさん処理しました。続けますか?',
|
||||
'assistant.error.offline': 'オフラインです。アシスタントにはインターネット接続が必要です。',
|
||||
'assistant.error.noBookingsFound': 'このファイルに予約は見つかりませんでした。',
|
||||
'assistant.error.importUnavailable': 'ここでは予約ファイルを読み込めません。',
|
||||
'assistant.error.generic': '問題が発生しました。',
|
||||
'assistant.errorAction.retry': '再試行',
|
||||
'assistant.errorAction.continueHere': 'ここで続ける',
|
||||
'assistant.errorAction.continue': '続ける',
|
||||
'assistant.errorAction.signIn': 'もう一度サインイン',
|
||||
'assistant.errorAction.viewUsage': '使用状況を見る',
|
||||
'assistant.errorAction.otherProvider': '別のプロバイダーを選ぶ',
|
||||
'assistant.errorAction.replaceKey': 'キーを差し替え',
|
||||
'assistant.errorAction.connectOwn': '自分のプロバイダーを接続',
|
||||
'assistant.admin.policy': '支払う人',
|
||||
'assistant.admin.policy.instance': '全員分を自分が支払う',
|
||||
'assistant.admin.policy.instanceOrUser': '自分が支払い、各自の接続も可',
|
||||
'assistant.admin.policy.user': '各自が自分のアカウントを使う',
|
||||
'assistant.admin.policy.instanceHint': '全員が下の接続を使い、自分の接続は追加できません。',
|
||||
'assistant.admin.policy.instanceOrUserHint': '自分の接続を追加しない限り、全員が下の接続を使います。',
|
||||
'assistant.admin.policy.userHint': '各自が自分のプロバイダーを接続します。あなたに費用はかかりません。',
|
||||
'assistant.admin.connection': '全員向けの接続',
|
||||
'assistant.admin.connectionNone': 'まだ接続がありません。',
|
||||
'assistant.admin.connectionRemove': '接続を削除',
|
||||
'assistant.admin.custom': '独自サーバー',
|
||||
'assistant.admin.customHint': 'OpenAI 互換のサーバーなら何でも使えます(例: Ollama)。',
|
||||
'assistant.admin.baseUrl': 'サーバーのアドレス',
|
||||
'assistant.admin.apiKeyOptional': 'API キー(任意)',
|
||||
'assistant.admin.prefilled': 'AI 解析の設定を引き継ぎました。',
|
||||
'assistant.admin.dailyLimit': '1 人あたりの 1 日のメッセージ数',
|
||||
'assistant.admin.dailyLimitHint': 'あなたが支払っている間だけカウントされます。',
|
||||
'assistant.admin.retention': 'チャットの保存期間',
|
||||
'assistant.admin.retentionDays': '{count} 日',
|
||||
'assistant.admin.retentionForever': '無期限',
|
||||
'assistant.claude.title': 'Claude と接続',
|
||||
'assistant.claude.description': 'Claude の中で TREK を直接使えます。コネクタとして追加し、サインインすれば完了です。',
|
||||
'assistant.claude.open': 'Claude で開く',
|
||||
'assistant.claude.scan': 'またはスマートフォンでスキャン',
|
||||
'assistant.claude.copyUrl': 'アドレスをコピー',
|
||||
'assistant.claude.qrAlt': 'Claude に TREK を追加するための QR コード',
|
||||
'assistant.claude.notPublic':
|
||||
'Claude が TREK にアクセスできるのは、TREK がインターネット上で https で公開されている場合だけです。',
|
||||
'assistant.mobile.tile': 'TREK に質問',
|
||||
'assistant.mobile.tileHint': 'アシスタントと一緒に計画',
|
||||
'assistant.mobile.backToChat': 'チャットに戻る',
|
||||
'assistant.step.nearbyAt': '{place}の近くを検索中',
|
||||
'assistant.step.searchingFor': '{query}を検索中',
|
||||
'assistant.step.weatherAt': '{place}の天気を確認中',
|
||||
'assistant.change.timeline.show': '予定を表示',
|
||||
'assistant.change.timeline.hide': '予定を隠す',
|
||||
'assistant.change.timeline.new': '新規',
|
||||
'assistant.change.timeline.travel': '移動 約{count}分',
|
||||
'assistant.change.timeline.gap': '空き {count}分',
|
||||
'assistant.change.timeline.overlap': '{place}と重なっています',
|
||||
'assistant.change.timeline.moreDays': 'ほか{count}日',
|
||||
'assistant.change.viewOnMap': '地図で見る',
|
||||
'assistant.change.before': '変更前',
|
||||
'assistant.change.after': '変更後',
|
||||
'assistant.change.compare': '地図で比較',
|
||||
'assistant.change.undoAll': 'すべて元に戻す',
|
||||
'assistant.change.undoneAll': '{count}件の変更を元に戻しました',
|
||||
'assistant.change.undoAllPartial': '{done}件を元に戻し、{stale}件はその後変更されています',
|
||||
'assistant.change.undoAllFailed': '{count}件の変更を元に戻せませんでした',
|
||||
'assistant.place.strip': '見つかった場所',
|
||||
'assistant.place.addTo': '{day}に追加',
|
||||
'assistant.place.addToShort': '+ {n}日目',
|
||||
'assistant.place.pickDay': '日を選ぶ',
|
||||
'assistant.place.suggested': '提案済み',
|
||||
'assistant.place.inTrip': 'すでに旅行に含まれています',
|
||||
'assistant.place.showOnMap': '地図で表示',
|
||||
'assistant.place.openNow': '営業中',
|
||||
'assistant.place.closed': '休業',
|
||||
'assistant.place.fromPrev': '前の立ち寄り先から{distance}',
|
||||
'assistant.place.more': 'ほか{count}件',
|
||||
'assistant.mention.days': '日',
|
||||
'assistant.mention.places': '場所',
|
||||
'assistant.mention.bookings': '予約',
|
||||
'assistant.mention.none': '見つかりません',
|
||||
'assistant.mention.hint': '@を入力すると日、場所、予約を指定できます。',
|
||||
'assistant.attach.dropPlace': 'ここに場所をドロップ',
|
||||
'assistant.weather.strip': '{count}日間の天気',
|
||||
'assistant.weather.rain': '降水確率 {pct}%',
|
||||
'assistant.settings.followMap': '地図がアシスタントに追従',
|
||||
'assistant.settings.followMapHint': 'アシスタントが検索したり場所を提案したりする地点へ地図が移動します。',
|
||||
'assistant.error.decideFirst': '先に未決の提案について決めてください。',
|
||||
'assistant.error.providerLimit': '{provider}の上限に達しました。しばらくしてからもう一度お試しください。',
|
||||
'assistant.hint.title': '確認しておきたい点',
|
||||
'assistant.hint.dismiss': 'このヒントを隠す',
|
||||
'assistant.hint.more': 'ほか{count}件',
|
||||
'assistant.hint.overlap': '{day}に{a}と{b}が重なっています',
|
||||
'assistant.hint.overlap.prompt': '{day}に{a}と{b}が重なっています。どう解決しましょうか?',
|
||||
'assistant.hint.closed': '{day}は{place}が休みです',
|
||||
'assistant.hint.closed.prompt': '{day}は{place}が休みです。代わりに何をしましょうか?',
|
||||
'assistant.hint.opensLate': '{day}の{place}は{time}開店です',
|
||||
'assistant.hint.closesEarly': '{day}の{place}は{time}に閉まります',
|
||||
'assistant.hint.outsideHours.prompt':
|
||||
'{day}の{place}への訪問が営業時間に合っていません。どう動かすのがいいでしょうか?',
|
||||
'assistant.hint.gap': '{day}: {from}から{count}時間空いています',
|
||||
'assistant.hint.gap.prompt': '{day}の{from}から{to}まで予定がありません。何が入りそうですか?',
|
||||
'assistant.hint.longLeg': '{from}と{to}は直線で{distance}離れています',
|
||||
'assistant.hint.longLeg.prompt': '{day}の{from}から{to}は遠すぎますか?どう行くのが一番いいですか?',
|
||||
'assistant.hint.tightLeg': '{from}と{to}の間は{count}分しかありません',
|
||||
'assistant.hint.tightLeg.prompt': '{day}に{from}から{to}へ移動する時間は足りますか?何を変えればいいですか?',
|
||||
'assistant.hint.noStay': '{day}から{count}泊分の宿がまだありません',
|
||||
'assistant.hint.noStay.prompt': '{day}からの宿がまだ決まっていません。おすすめはありますか?',
|
||||
'assistant.hint.unplacedBooking': '{title}はどの日にも入っていません',
|
||||
'assistant.hint.unplacedBookingDay': '{title}はおそらく{day}の予定です',
|
||||
'assistant.hint.unplacedBookings': '{count}件の予約がどの日にも入っていません',
|
||||
'assistant.hint.unplacedBooking.prompt': 'どの日にも入っていない予約はどれで、どこに入れるべきですか?',
|
||||
'assistant.hint.menuOn': 'ヒントを表示',
|
||||
'assistant.hint.menuOff': 'ヒントを表示しない',
|
||||
'assistant.launcher.withHints': 'アシスタント、ヒント{count}件',
|
||||
'assistant.mobile.tileHints': 'プランに確認したい点が{count}件あります',
|
||||
'assistant.share.button': '旅行チャットで共有',
|
||||
'assistant.share.title': '旅行チャットで共有',
|
||||
'assistant.share.who': 'この旅行のメンバー全員に、アシスタントからのメッセージとして表示されます。',
|
||||
'assistant.share.includeChanges': '適用した変更を添える',
|
||||
'assistant.share.changesHeading': '適用済み:',
|
||||
'assistant.share.truncated': '{count}文字に短縮しました',
|
||||
'assistant.share.send': '共有',
|
||||
'assistant.share.done': '旅行チャットで共有しました',
|
||||
'assistant.share.shared': '共有済み',
|
||||
'assistant.share.failed': '共有できませんでした。少ししてからもう一度お試しください。',
|
||||
'assistant.share.chatOff': '旅行チャットはオフになっています。',
|
||||
};
|
||||
export default assistant;
|
||||
@@ -101,5 +101,7 @@ const collab: TranslationStrings = {
|
||||
'collab.chat.attachImages': '画像を添付',
|
||||
'collab.chat.attachedImage': '添付画像',
|
||||
'collab.chat.closeImage': '画像を閉じる',
|
||||
'collab.chat.fromAssistant': 'アシスタントから',
|
||||
'collab.chat.fromAssistantHint': '{name}さんがアシスタントの回答を共有しました',
|
||||
};
|
||||
export default collab;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import admin from './admin';
|
||||
import airport from './airport';
|
||||
import assistant from './assistant';
|
||||
import atlas from './atlas';
|
||||
import backup from './backup';
|
||||
import budget from './budget';
|
||||
@@ -116,5 +117,6 @@ const locale = {
|
||||
...dawarich,
|
||||
...docsync,
|
||||
...ferratas,
|
||||
...assistant,
|
||||
};
|
||||
export default locale;
|
||||
|
||||
@@ -83,6 +83,7 @@ const oauth: TranslationStrings = {
|
||||
'oauth.authorize.loginButton': 'Sign in to TREK', // en-fallback
|
||||
'oauth.authorize.requestLabel': 'Authorization Request', // en-fallback
|
||||
'oauth.authorize.requestDescription': 'This application is requesting access to your TREK account.', // en-fallback
|
||||
'oauth.authorize.verifiedHost': '確認済みのドメイン: {host}',
|
||||
'oauth.authorize.trustNote': 'Only grant access to applications you trust. Your data stays on your server.', // en-fallback
|
||||
'oauth.authorize.selectScope': 'Select at least one scope', // en-fallback
|
||||
'oauth.authorize.approveScopes': '承認(スコープ {count} 件)',
|
||||
|
||||
@@ -650,6 +650,9 @@ const admin: TranslationStrings = {
|
||||
'사용자가 각자 연결한 Dawarich 인스턴스에서 방문 기록과 기록된 경로를 읽어옵니다',
|
||||
'admin.addons.catalog.llm_parsing.name': 'AI 분석',
|
||||
'admin.addons.catalog.llm_parsing.description': '기본 분석기가 읽지 못하는 예약을 원하는 AI 모델로 읽어냅니다',
|
||||
'admin.addons.catalog.ai_assistant.name': 'AI 어시스턴트',
|
||||
'admin.addons.catalog.ai_assistant.description':
|
||||
'여행마다 질문에 답하고 변경 사항을 제안하는 채팅. 변경은 직접 확인한 뒤에 적용됩니다',
|
||||
'admin.addons.llm.vision.auto': '자동',
|
||||
'admin.addons.llm.vision.on': '예',
|
||||
'admin.addons.llm.vision.off': '아니요',
|
||||
|
||||
@@ -0,0 +1,318 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistant: TranslationStrings = {
|
||||
'assistant.launcher.tooltip': '어시스턴트',
|
||||
'assistant.launcher.ctrlKey': 'Ctrl',
|
||||
'assistant.picker.title': '어떤 여행인가요?',
|
||||
'assistant.picker.empty': '먼저 여행을 만들어 주세요. 그러면 어시스턴트가 계획을 도와드립니다.',
|
||||
'assistant.picker.now': '지금',
|
||||
'assistant.picker.next': '다음',
|
||||
'assistant.window.title': '어시스턴트',
|
||||
'assistant.window.log': '어시스턴트와의 대화',
|
||||
'assistant.window.menu': '추가 옵션',
|
||||
'assistant.window.settings': 'AI 설정',
|
||||
'assistant.window.deleteThread': '채팅 삭제',
|
||||
'assistant.window.deleteConfirm': '이 채팅을 삭제할까요? 되돌릴 수 없습니다.',
|
||||
'assistant.setup.question': '무엇과 대화할까요?',
|
||||
'assistant.setup.beta': '베타',
|
||||
'assistant.setup.chatgptNote': '공식 방법은 API 키입니다. ChatGPT 로그인은 아직 베타입니다.',
|
||||
'assistant.setup.keySubtitle': 'Anthropic, OpenAI, Gemini 또는 OpenRouter',
|
||||
'assistant.setup.adminOnly': '관리자가 설정합니다.',
|
||||
'assistant.setup.forEveryone': '모두를 위해 설정',
|
||||
'assistant.connect.title': 'AI 어시스턴트',
|
||||
'assistant.connect.description':
|
||||
'여행 내용을 알고, 직접 확인한 뒤에 적용되는 변경을 제안하는 어시스턴트와 채팅하세요.',
|
||||
'assistant.connect.chatgpt': 'ChatGPT로 로그인',
|
||||
'assistant.connect.chatgptHint': '사용 중인 ChatGPT 요금제를 씁니다. 키가 필요 없습니다.',
|
||||
'assistant.connect.chatgptStep1': '새 탭에서 ChatGPT에 로그인하세요.',
|
||||
'assistant.connect.chatgptOpen': 'ChatGPT 로그인 열기',
|
||||
'assistant.connect.chatgptStep2':
|
||||
'그다음 브라우저에 오류 페이지가 표시됩니다. 정상입니다. 위쪽의 주소를 복사해서 돌아오세요.',
|
||||
'assistant.connect.chatgptPaste': '주소 붙여넣기',
|
||||
'assistant.connect.chatgptWrong': '올바른 주소가 아닙니다. 127.0.0.1로 시작하는 주소입니다.',
|
||||
'assistant.connect.chatgptExpired': '시간이 너무 오래 걸렸습니다. 다시 로그인하세요.',
|
||||
'assistant.connect.chatgptOtherAccount': '다른 ChatGPT 계정입니다. 먼저 연결을 해제하세요.',
|
||||
'assistant.connect.key': 'API 키 붙여넣기',
|
||||
'assistant.connect.keyPlaceholder': '여기에 키를 붙여넣으세요',
|
||||
'assistant.connect.detected': '감지됨: {provider}',
|
||||
'assistant.connect.whichProvider': '이 키는 어느 제공업체의 키인가요?',
|
||||
'assistant.connect.testing': '연결 확인 중',
|
||||
'assistant.connect.ok': '연결되었습니다. 시작해 볼까요?',
|
||||
'assistant.connect.openAssistant': '어시스턴트 열기',
|
||||
'assistant.connect.badKey': '이 키는 작동하지 않습니다.',
|
||||
'assistant.connect.unknownModel': '이 모델은 여기에서 제공되지 않습니다.',
|
||||
'assistant.connect.getKey': '키 받기',
|
||||
'assistant.connect.hintAnthropic': 'Claude Max와 Team에는 API 크레딧이 포함되어 있습니다.',
|
||||
'assistant.connect.hintOpenai': 'OpenAI 대시보드에서 키를 만드세요.',
|
||||
'assistant.connect.hintGemini': 'AI Studio에는 분당, 일일 한도가 있는 무료 등급이 있으며, 여기서는 Google이 보낸 내용을 제품 개선에 사용할 수 있습니다. 결제를 켜면 한도가 높아지고 이는 적용되지 않습니다.',
|
||||
'assistant.connect.hintOpenrouter': 'OpenRouter로 로그인하면 키를 복사할 필요가 없습니다.',
|
||||
'assistant.connect.openrouter': 'OpenRouter 연결',
|
||||
'assistant.connect.save': '연결',
|
||||
'assistant.connect.model': '모델',
|
||||
'assistant.connect.modelHint': '도구를 사용할 수 있는 모델만 표시됩니다.',
|
||||
'assistant.connect.free': '무료 등급 제공',
|
||||
'assistant.connect.paid': '사용량 기반 결제',
|
||||
'assistant.model.recommended': '추천',
|
||||
'assistant.model.free': '무료',
|
||||
'assistant.connect.connected': '{provider}에 연결됨',
|
||||
'assistant.connect.providedByAdmin': '관리자가 제공',
|
||||
'assistant.connect.disconnect': '연결 해제',
|
||||
'assistant.connect.disconnectConfirm': '{provider} 연결을 해제할까요? 채팅은 그대로 남습니다.',
|
||||
'assistant.connect.signOut': '로그아웃',
|
||||
'assistant.connect.statusOk': '연결됨',
|
||||
'assistant.connect.statusNeedsReauth': '로그인 만료',
|
||||
'assistant.connect.statusSignedOut': '로그아웃됨',
|
||||
'assistant.connect.statusError': '연결할 수 없음',
|
||||
'assistant.connect.quota': '오늘 {count}개 중 {used}개 메시지 사용',
|
||||
'assistant.connect.claudeLink': 'Claude를 사용하시나요? Claude에서 연결하세요',
|
||||
'assistant.privacy.what': '무엇이 공유되나요?',
|
||||
'assistant.privacy.banner': '메시지와 어시스턴트가 읽는 여행 데이터는 {provider}(으)로 전송됩니다.',
|
||||
'assistant.privacy.ok': '확인',
|
||||
'assistant.privacy.shared': '메시지와 어시스턴트가 읽는 장소, 일정, 예약 같은 여행 정보.',
|
||||
'assistant.privacy.notShared': '비밀번호, 파일, 다른 여행은 공유되지 않습니다.',
|
||||
'assistant.privacy.provider': '{provider}은(는) 자체 개인정보 처리방침에 따라 이를 처리합니다.',
|
||||
'assistant.empty.title': '무엇을 계획할까요?',
|
||||
'assistant.empty.readOnly': '이 여행은 보기만 할 수 있어서 질문에는 답하지만 아무것도 바꿀 수 없어요.',
|
||||
'assistant.suggest.planFirstDays': '처음 며칠 일정을 짜 줘',
|
||||
'assistant.suggest.mustSee': '여기서 놓치면 안 되는 건?',
|
||||
'assistant.suggest.packingList': '이 여행용 짐 목록을 만들어 줘',
|
||||
'assistant.suggest.emptyDays': '아직 비어 있는 날은?',
|
||||
'assistant.suggest.rainPlan': '비가 오면 뭘 할 수 있을까?',
|
||||
'assistant.suggest.today': '오늘 일정은?',
|
||||
'assistant.suggest.eatNearby': '근처에서 먹을 곳을 찾아 줘',
|
||||
'assistant.suggest.weather': '날씨는 어때?',
|
||||
'assistant.suggest.openTodos': '할 일 목록에 아직 남은 건?',
|
||||
'assistant.suggest.settleUp': '누가 누구에게 얼마를 줘야 해?',
|
||||
'assistant.suggest.recap': '우리 여행을 요약해 줘',
|
||||
'assistant.suggest.nearPlace': '{place} 근처에 또 뭐가 있어?',
|
||||
'assistant.suggest.placeTips': '{place} 방문 팁',
|
||||
'assistant.suggest.planDay': '{day} 일정을 짜 줘',
|
||||
'assistant.suggest.dayTooFull': '{day} 일정이 너무 빡빡해?',
|
||||
'assistant.thread.scrollDown': '최신 메시지로 이동',
|
||||
'assistant.attach.button': '예약 첨부',
|
||||
'assistant.attach.drop': '여기에 놓으면 예약을 읽어 드려요',
|
||||
'assistant.attach.unsupported': '이 파일 형식은 읽을 수 없어요. PDF, 이메일, 이미지 또는 지갑 패스를 사용해 보세요.',
|
||||
'assistant.attach.tooLarge': '이 파일은 10MB보다 큽니다.',
|
||||
'assistant.composer.placeholder': '{trip}에 대해 무엇이든 물어보세요',
|
||||
'assistant.composer.send': '보내기',
|
||||
'assistant.composer.stop': '중지',
|
||||
'assistant.composer.decideFirst': '먼저 위의 변경을 적용하거나 취소하세요.',
|
||||
'assistant.context.ask': '질문',
|
||||
'assistant.context.remove': '컨텍스트 제거',
|
||||
'assistant.step.reading': '계획을 읽는 중',
|
||||
'assistant.step.searching': '장소를 검색하는 중',
|
||||
'assistant.step.details': '세부 정보를 찾는 중',
|
||||
'assistant.step.nearby': '주변을 검색하는 중',
|
||||
'assistant.step.categories': '카테고리를 확인하는 중',
|
||||
'assistant.step.weather': '날씨를 확인하는 중',
|
||||
'assistant.step.todos': '할 일 목록을 읽는 중',
|
||||
'assistant.step.costs': '비용을 확인하는 중',
|
||||
'assistant.step.help': '도움말을 찾는 중',
|
||||
'assistant.step.readingBooking': '예약을 읽는 중',
|
||||
'assistant.step.working': '작업 중',
|
||||
'assistant.step.collapsed': '{count}단계',
|
||||
'assistant.change.title': '제안된 변경 사항',
|
||||
'assistant.change.doneTitle': '완료',
|
||||
'assistant.change.apply': '적용',
|
||||
'assistant.change.applyN': '{count}개 적용',
|
||||
'assistant.change.discard': '취소',
|
||||
'assistant.change.include': '이 변경 포함',
|
||||
'assistant.change.applied': '적용됨',
|
||||
'assistant.change.discarded': '취소됨',
|
||||
'assistant.change.failed': '적용하지 못했습니다.',
|
||||
'assistant.change.applying': '적용 중',
|
||||
'assistant.change.expired': '더 이상 사용할 수 없음',
|
||||
'assistant.change.undo': '되돌리기',
|
||||
'assistant.change.undone': '되돌림',
|
||||
'assistant.change.undoStale': '그 사이에 변경되었습니다. 직접 조정해 주세요.',
|
||||
'assistant.change.undoFailed': '되돌리지 못했습니다.',
|
||||
'assistant.change.showOnMap': '지도에 표시',
|
||||
'assistant.change.empty': '비어 있음',
|
||||
'assistant.change.pending': '{count}개의 변경이 확인을 기다리고 있습니다',
|
||||
'assistant.change.verb.addPlace': '장소 추가',
|
||||
'assistant.change.verb.addPlaceToDay': '날짜에 장소 추가',
|
||||
'assistant.change.verb.updatePlace': '장소 변경',
|
||||
'assistant.change.verb.assignPlace': '장소를 날짜에 배정',
|
||||
'assistant.change.verb.moveAssignment': '다른 날로 이동',
|
||||
'assistant.change.verb.removeFromDay': '이 날에서 빼기',
|
||||
'assistant.change.verb.addDayNote': '메모 추가',
|
||||
'assistant.change.verb.updateDayNote': '메모 변경',
|
||||
'assistant.change.verb.addPackingItem': '짐 목록에 추가',
|
||||
'assistant.change.verb.updatePackingItem': '짐 항목 변경',
|
||||
'assistant.change.verb.addTodo': '할 일 추가',
|
||||
'assistant.change.verb.addBudgetItem': '지출 추가',
|
||||
'assistant.change.verb.addReservation': '예약 추가',
|
||||
'assistant.change.verb.importBooking': '파일에서 예약 추가',
|
||||
'assistant.change.label.name': '이름',
|
||||
'assistant.change.label.description': '설명',
|
||||
'assistant.change.label.address': '주소',
|
||||
'assistant.change.label.coordinates': '지도상 위치',
|
||||
'assistant.change.label.category': '카테고리',
|
||||
'assistant.change.label.day': '날짜',
|
||||
'assistant.change.label.time': '시간',
|
||||
'assistant.change.label.endTime': '종료',
|
||||
'assistant.change.label.notes': '메모',
|
||||
'assistant.change.label.website': '웹사이트',
|
||||
'assistant.change.label.phone': '전화',
|
||||
'assistant.change.label.email': '이메일',
|
||||
'assistant.change.label.openingHours': '영업시간',
|
||||
'assistant.change.label.price': '가격',
|
||||
'assistant.change.label.currency': '통화',
|
||||
'assistant.change.label.stopType': '경유지 유형',
|
||||
'assistant.change.label.text': '텍스트',
|
||||
'assistant.change.label.icon': '아이콘',
|
||||
'assistant.change.label.color': '색상',
|
||||
'assistant.change.label.position': '위치',
|
||||
'assistant.change.label.quantity': '수량',
|
||||
'assistant.change.label.weight': '무게',
|
||||
'assistant.change.label.bag': '가방',
|
||||
'assistant.change.label.packed': '챙김',
|
||||
'assistant.change.label.dueDate': '기한',
|
||||
'assistant.change.label.priority': '우선순위',
|
||||
'assistant.change.label.assignee': '담당자',
|
||||
'assistant.change.label.type': '유형',
|
||||
'assistant.change.label.location': '장소',
|
||||
'assistant.change.label.link': '링크',
|
||||
'assistant.change.label.confirmation': '확인 번호',
|
||||
'assistant.change.label.checkIn': '체크인',
|
||||
'assistant.change.label.checkOut': '체크아웃',
|
||||
'assistant.change.label.stay': '숙박',
|
||||
'assistant.change.label.members': '나눌 사람',
|
||||
'assistant.change.label.route': '구간',
|
||||
'assistant.history.open': '최근 채팅',
|
||||
'assistant.history.new': '새 채팅',
|
||||
'assistant.history.empty': '아직 채팅이 없습니다.',
|
||||
'assistant.history.retention': '채팅은 {count}일 후에 삭제됩니다.',
|
||||
'assistant.history.forever': '채팅은 직접 삭제할 때까지 보관됩니다.',
|
||||
'assistant.error.notConnected': '어시스턴트가 아직 연결되지 않았습니다.',
|
||||
'assistant.error.busy': '이미 다른 창에서 실행 중입니다.',
|
||||
'assistant.error.dailyLimit': '오늘은 여기까지입니다. 내일 만나요.',
|
||||
'assistant.error.rateLimited': '조금만 천천히 해 주세요. 1분 후에 다시 시도하세요.',
|
||||
'assistant.error.needsReauth': '{provider} 로그인이 만료되었습니다.',
|
||||
'assistant.error.usageLimit': 'ChatGPT 사용량을 모두 썼습니다.',
|
||||
'assistant.error.notEligible': '이 ChatGPT 계정은 여기에서 사용할 수 없습니다.',
|
||||
'assistant.error.invalidKey': '{provider} 키가 더 이상 작동하지 않습니다.',
|
||||
'assistant.error.providerDown': '{provider}이(가) 지금 응답하지 않습니다.',
|
||||
'assistant.error.maxSteps': '한 번에 많은 작업을 했습니다. 계속할까요?',
|
||||
'assistant.error.offline': '오프라인입니다. 어시스턴트는 인터넷이 필요합니다.',
|
||||
'assistant.error.noBookingsFound': '이 파일에서 예약을 찾지 못했습니다.',
|
||||
'assistant.error.importUnavailable': '여기서는 예약 파일을 읽을 수 없습니다.',
|
||||
'assistant.error.generic': '문제가 발생했습니다.',
|
||||
'assistant.errorAction.retry': '다시 시도',
|
||||
'assistant.errorAction.continueHere': '여기서 계속',
|
||||
'assistant.errorAction.continue': '계속',
|
||||
'assistant.errorAction.signIn': '다시 로그인',
|
||||
'assistant.errorAction.viewUsage': '사용량 보기',
|
||||
'assistant.errorAction.otherProvider': '다른 제공자 선택',
|
||||
'assistant.errorAction.replaceKey': '키 교체',
|
||||
'assistant.errorAction.connectOwn': '내 제공자 연결',
|
||||
'assistant.admin.policy': '비용 부담',
|
||||
'assistant.admin.policy.instance': '모두의 비용을 내가 부담',
|
||||
'assistant.admin.policy.instanceOrUser': '내가 부담, 개인 연결 허용',
|
||||
'assistant.admin.policy.user': '각자 자신의 계정 사용',
|
||||
'assistant.admin.policy.instanceHint': '모두가 아래 연결을 사용하며 직접 추가할 수 없습니다.',
|
||||
'assistant.admin.policy.instanceOrUserHint': '직접 연결하지 않는 한 모두가 아래 연결을 사용합니다.',
|
||||
'assistant.admin.policy.userHint': '각자 자신의 제공자를 연결합니다. 나에게는 비용이 청구되지 않습니다.',
|
||||
'assistant.admin.connection': '모두를 위한 연결',
|
||||
'assistant.admin.connectionNone': '아직 연결이 없습니다.',
|
||||
'assistant.admin.connectionRemove': '연결 제거',
|
||||
'assistant.admin.custom': '자체 서버',
|
||||
'assistant.admin.customHint': 'OpenAI 호환 서버라면 무엇이든 됩니다. 예: Ollama.',
|
||||
'assistant.admin.baseUrl': '서버 주소',
|
||||
'assistant.admin.apiKeyOptional': 'API 키 (선택)',
|
||||
'assistant.admin.prefilled': 'AI 분석 설정에서 가져왔습니다.',
|
||||
'assistant.admin.dailyLimit': '1인당 하루 메시지 수',
|
||||
'assistant.admin.dailyLimitHint': '내가 비용을 부담하는 동안에만 계산됩니다.',
|
||||
'assistant.admin.retention': '채팅 보관',
|
||||
'assistant.admin.retentionDays': '{count}일',
|
||||
'assistant.admin.retentionForever': '영구',
|
||||
'assistant.claude.title': 'Claude와 연결',
|
||||
'assistant.claude.description': 'Claude 안에서 바로 TREK을 사용하세요. 커넥터로 추가하고 로그인하면 끝입니다.',
|
||||
'assistant.claude.open': 'Claude에서 열기',
|
||||
'assistant.claude.scan': '또는 휴대폰으로 스캔',
|
||||
'assistant.claude.copyUrl': '주소 복사',
|
||||
'assistant.claude.qrAlt': 'Claude에 TREK을 추가하는 QR 코드',
|
||||
'assistant.claude.notPublic': 'TREK이 인터넷에서 https로 접속 가능할 때만 Claude가 TREK에 연결할 수 있습니다.',
|
||||
'assistant.mobile.tile': 'TREK에 질문',
|
||||
'assistant.mobile.tileHint': '어시스턴트와 함께 계획',
|
||||
'assistant.mobile.backToChat': '채팅으로 돌아가기',
|
||||
'assistant.step.nearbyAt': '{place} 근처를 찾는 중',
|
||||
'assistant.step.searchingFor': '{query} 찾는 중',
|
||||
'assistant.step.weatherAt': '{place}의 날씨 확인 중',
|
||||
'assistant.change.timeline.show': '일정 보기',
|
||||
'assistant.change.timeline.hide': '일정 숨기기',
|
||||
'assistant.change.timeline.new': '새 항목',
|
||||
'assistant.change.timeline.travel': '이동 약 {count}분',
|
||||
'assistant.change.timeline.gap': '{count}분 비어 있음',
|
||||
'assistant.change.timeline.overlap': '{place}와 겹침',
|
||||
'assistant.change.timeline.moreDays': '{count}일 더',
|
||||
'assistant.change.viewOnMap': '지도에서 보기',
|
||||
'assistant.change.before': '변경 전',
|
||||
'assistant.change.after': '변경 후',
|
||||
'assistant.change.compare': '지도에서 비교',
|
||||
'assistant.change.undoAll': '모두 되돌리기',
|
||||
'assistant.change.undoneAll': '변경 {count}개를 되돌렸습니다',
|
||||
'assistant.change.undoAllPartial': '{done}개 되돌림, {stale}개는 그사이 변경됨',
|
||||
'assistant.change.undoAllFailed': '변경 {count}개를 되돌리지 못했습니다',
|
||||
'assistant.place.strip': '찾은 장소',
|
||||
'assistant.place.addTo': '{day}에 추가',
|
||||
'assistant.place.addToShort': '+ {n}일차',
|
||||
'assistant.place.pickDay': '날짜 선택',
|
||||
'assistant.place.suggested': '제안됨',
|
||||
'assistant.place.inTrip': '이미 여행에 있음',
|
||||
'assistant.place.showOnMap': '지도에서',
|
||||
'assistant.place.openNow': '영업 중',
|
||||
'assistant.place.closed': '영업 종료',
|
||||
'assistant.place.fromPrev': '이전 장소에서 {distance}',
|
||||
'assistant.place.more': '{count}개 더',
|
||||
'assistant.mention.days': '날짜',
|
||||
'assistant.mention.places': '장소',
|
||||
'assistant.mention.bookings': '예약',
|
||||
'assistant.mention.none': '찾은 결과 없음',
|
||||
'assistant.mention.hint': '@를 입력해 날짜, 장소, 예약을 언급하세요.',
|
||||
'assistant.attach.dropPlace': '장소를 여기에 놓으세요',
|
||||
'assistant.weather.strip': '{count}일간 날씨',
|
||||
'assistant.weather.rain': '비 {pct}%',
|
||||
'assistant.settings.followMap': '지도가 어시스턴트를 따라감',
|
||||
'assistant.settings.followMapHint': '어시스턴트가 검색하고 장소를 제안하는 곳으로 지도가 이동합니다.',
|
||||
'assistant.error.decideFirst': '먼저 남은 제안을 결정하세요.',
|
||||
'assistant.error.providerLimit': '{provider}의 한도에 도달했습니다. 나중에 다시 시도하세요.',
|
||||
'assistant.hint.title': '확인해 볼 것',
|
||||
'assistant.hint.dismiss': '이 알림 숨기기',
|
||||
'assistant.hint.more': '{count}개 더',
|
||||
'assistant.hint.overlap': '{day}에 {a}와 {b}가 겹칩니다',
|
||||
'assistant.hint.overlap.prompt': '{day}에 {a}와 {b}가 겹칩니다. 어떻게 해결할까요?',
|
||||
'assistant.hint.closed': '{day}에는 {place}가 문을 닫습니다',
|
||||
'assistant.hint.closed.prompt': '{day}에는 {place}가 문을 닫습니다. 대신 무엇을 할까요?',
|
||||
'assistant.hint.opensLate': '{day}에 {place}는 {time}에야 문을 엽니다',
|
||||
'assistant.hint.closesEarly': '{day}에 {place}는 {time}에 문을 닫습니다',
|
||||
'assistant.hint.outsideHours.prompt': '{day} {place} 방문이 영업시간과 맞지 않습니다. 어떻게 옮기는 게 좋을까요?',
|
||||
'assistant.hint.gap': '{day}: {from}부터 {count}시간 비어 있음',
|
||||
'assistant.hint.gap.prompt': '{day} {from}부터 {to}까지 계획이 없습니다. 무엇을 넣으면 좋을까요?',
|
||||
'assistant.hint.longLeg': '{from}와 {to}는 직선거리로 {distance} 떨어져 있습니다',
|
||||
'assistant.hint.longLeg.prompt': '{day}에 {from}에서 {to}까지 너무 먼가요? 어떻게 가는 게 가장 좋을까요?',
|
||||
'assistant.hint.tightLeg': '{from}와 {to} 사이가 {count}분뿐입니다',
|
||||
'assistant.hint.tightLeg.prompt': '{day}에 {from}에서 {to}까지 갈 시간이 충분한가요? 무엇을 바꿔야 할까요?',
|
||||
'assistant.hint.noStay': '{day}부터 {count}박 숙소가 아직 없습니다',
|
||||
'assistant.hint.noStay.prompt': '{day}부터 아직 숙소가 필요합니다. 어떤 곳을 추천하나요?',
|
||||
'assistant.hint.unplacedBooking': '{title}이(가) 아직 어느 날에도 없습니다',
|
||||
'assistant.hint.unplacedBookingDay': '{title}은(는) {day} 일정으로 보입니다',
|
||||
'assistant.hint.unplacedBookings': '예약 {count}개가 아직 어느 날에도 없습니다',
|
||||
'assistant.hint.unplacedBooking.prompt': '어느 날에도 없는 예약은 무엇이고, 어디에 넣어야 할까요?',
|
||||
'assistant.hint.menuOn': '알림 보기',
|
||||
'assistant.hint.menuOff': '알림 끄기',
|
||||
'assistant.launcher.withHints': '어시스턴트, 알림 {count}개',
|
||||
'assistant.mobile.tileHints': '계획에서 확인할 것 {count}개',
|
||||
'assistant.share.button': '여행 채팅에 공유',
|
||||
'assistant.share.title': '여행 채팅에 공유',
|
||||
'assistant.share.who': '이 여행의 모든 사람이 어시스턴트가 보낸 것으로 표시된 이 메시지를 봅니다.',
|
||||
'assistant.share.includeChanges': '적용한 변경 내용 첨부',
|
||||
'assistant.share.changesHeading': '적용됨:',
|
||||
'assistant.share.truncated': '{count}자로 줄였습니다',
|
||||
'assistant.share.send': '공유',
|
||||
'assistant.share.done': '여행 채팅에 공유했습니다',
|
||||
'assistant.share.shared': '공유됨',
|
||||
'assistant.share.failed': '공유하지 못했습니다. 잠시 후 다시 시도하세요.',
|
||||
'assistant.share.chatOff': '여행 채팅이 꺼져 있습니다.',
|
||||
};
|
||||
export default assistant;
|
||||
@@ -101,5 +101,7 @@ const collab: TranslationStrings = {
|
||||
'collab.chat.attachImages': '이미지 첨부',
|
||||
'collab.chat.attachedImage': '첨부된 이미지',
|
||||
'collab.chat.closeImage': '이미지 닫기',
|
||||
'collab.chat.fromAssistant': '어시스턴트 답변',
|
||||
'collab.chat.fromAssistantHint': '{name}님이 어시스턴트의 답변을 공유했습니다',
|
||||
};
|
||||
export default collab;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import admin from './admin';
|
||||
import airport from './airport';
|
||||
import assistant from './assistant';
|
||||
import atlas from './atlas';
|
||||
import backup from './backup';
|
||||
import budget from './budget';
|
||||
@@ -116,5 +117,6 @@ const locale = {
|
||||
...dawarich,
|
||||
...docsync,
|
||||
...ferratas,
|
||||
...assistant,
|
||||
};
|
||||
export default locale;
|
||||
|
||||
@@ -81,6 +81,7 @@ const oauth: TranslationStrings = {
|
||||
'oauth.authorize.loginButton': 'Sign in to TREK', // en-fallback
|
||||
'oauth.authorize.requestLabel': 'Authorization Request', // en-fallback
|
||||
'oauth.authorize.requestDescription': 'This application is requesting access to your TREK account.', // en-fallback
|
||||
'oauth.authorize.verifiedHost': '확인된 도메인: {host}',
|
||||
'oauth.authorize.trustNote': 'Only grant access to applications you trust. Your data stays on your server.', // en-fallback
|
||||
'oauth.authorize.selectScope': 'Select at least one scope', // en-fallback
|
||||
'oauth.authorize.approveScopes': '승인 (권한 범위 {count}개)',
|
||||
|
||||
@@ -641,6 +641,9 @@ const admin: TranslationStrings = {
|
||||
'admin.addons.catalog.llm_parsing.name': 'AI-analyse',
|
||||
'admin.addons.catalog.llm_parsing.description':
|
||||
'Leest boekingen die de ingebouwde parser niet begrijpt, met een AI-model naar keuze',
|
||||
'admin.addons.catalog.ai_assistant.name': 'AI-assistent',
|
||||
'admin.addons.catalog.ai_assistant.description':
|
||||
'Een chat in elke reis die vragen beantwoordt en wijzigingen voorstelt die jij bevestigt',
|
||||
'admin.addons.llm.vision.auto': 'Automatisch',
|
||||
'admin.addons.llm.vision.on': 'Ja',
|
||||
'admin.addons.llm.vision.off': 'Nee',
|
||||
|
||||
@@ -0,0 +1,343 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistant: TranslationStrings = {
|
||||
'assistant.launcher.tooltip': 'Assistent',
|
||||
'assistant.launcher.ctrlKey': 'Ctrl', // same-as-en
|
||||
'assistant.picker.title': 'Welke reis?',
|
||||
'assistant.picker.empty': 'Maak eerst een reis aan, dan helpt de assistent je met plannen.',
|
||||
'assistant.picker.now': 'Nu',
|
||||
'assistant.picker.next': 'Hierna',
|
||||
'assistant.window.title': 'Assistent',
|
||||
'assistant.window.log': 'Gesprek met de assistent',
|
||||
'assistant.window.menu': 'Meer opties',
|
||||
'assistant.window.settings': 'AI-instellingen',
|
||||
'assistant.window.deleteThread': 'Chat verwijderen',
|
||||
'assistant.window.deleteConfirm': 'Deze chat verwijderen? Dit kan niet ongedaan worden gemaakt.',
|
||||
'assistant.setup.question': 'Waarmee wil je chatten?',
|
||||
'assistant.setup.beta': 'Bèta',
|
||||
'assistant.setup.chatgptNote': 'Een API-sleutel is de officiële manier. Inloggen met ChatGPT is nog in bèta.',
|
||||
'assistant.setup.keySubtitle': 'Anthropic, OpenAI, Gemini of OpenRouter',
|
||||
'assistant.setup.adminOnly': 'Je admin stelt dit in.',
|
||||
'assistant.setup.forEveryone': 'Voor iedereen instellen',
|
||||
'assistant.connect.title': 'AI-assistent',
|
||||
'assistant.connect.description':
|
||||
'Chat met een assistent die je reis kent en wijzigingen voorstelt die jij bevestigt.',
|
||||
'assistant.connect.chatgpt': 'Inloggen met ChatGPT',
|
||||
'assistant.connect.chatgptHint': 'Gebruikt je ChatGPT-abonnement. Geen sleutel nodig.',
|
||||
'assistant.connect.chatgptStep1': 'Log in bij ChatGPT in het nieuwe tabblad.',
|
||||
'assistant.connect.chatgptOpen': 'ChatGPT-login openen',
|
||||
'assistant.connect.chatgptStep2':
|
||||
'Daarna toont je browser een foutpagina. Dat hoort zo. Kopieer het adres bovenaan en kom terug.',
|
||||
'assistant.connect.chatgptPaste': 'Adres plakken',
|
||||
'assistant.connect.chatgptWrong': 'Dat is niet het juiste adres. Het begint met 127.0.0.1.',
|
||||
'assistant.connect.chatgptExpired': 'Dat duurde te lang. Log opnieuw in.',
|
||||
'assistant.connect.chatgptOtherAccount': 'Dit is een ander ChatGPT-account. Verwijder eerst de koppeling.',
|
||||
'assistant.connect.key': 'API-sleutel plakken',
|
||||
'assistant.connect.keyPlaceholder': 'Plak hier je sleutel',
|
||||
'assistant.connect.detected': 'Herkend: {provider}',
|
||||
'assistant.connect.whichProvider': 'Van welke aanbieder is deze sleutel?',
|
||||
'assistant.connect.testing': 'Verbinding controleren',
|
||||
'assistant.connect.ok': 'Verbonden. Aan de slag.',
|
||||
'assistant.connect.openAssistant': 'Assistent openen',
|
||||
'assistant.connect.badKey': 'Deze sleutel werkt niet.',
|
||||
'assistant.connect.unknownModel': 'Dit model wordt hier niet aangeboden.',
|
||||
'assistant.connect.getKey': 'Sleutel aanvragen',
|
||||
'assistant.connect.hintAnthropic': 'Claude Max en Team bevatten API-tegoed.',
|
||||
'assistant.connect.hintOpenai': 'Maak een sleutel aan in je OpenAI-dashboard.',
|
||||
'assistant.connect.hintGemini': 'AI Studio heeft een gratis niveau met limieten per minuut en per dag, waarin Google wat je stuurt mag gebruiken om zijn producten te verbeteren. Met facturering ingeschakeld zijn de limieten hoger en geldt dat niet.',
|
||||
'assistant.connect.hintOpenrouter': 'Log in met OpenRouter, geen sleutel om te kopiëren.',
|
||||
'assistant.connect.openrouter': 'OpenRouter koppelen',
|
||||
'assistant.connect.save': 'Koppelen',
|
||||
'assistant.connect.model': 'Model', // same-as-en
|
||||
'assistant.connect.modelHint': 'Alleen modellen die tools kunnen gebruiken staan in de lijst.',
|
||||
'assistant.connect.free': 'Gratis niveau beschikbaar',
|
||||
'assistant.connect.paid': 'Betalen naar gebruik',
|
||||
'assistant.model.recommended': 'Aanbevolen',
|
||||
'assistant.model.free': 'Gratis',
|
||||
'assistant.connect.connected': 'Verbonden met {provider}',
|
||||
'assistant.connect.providedByAdmin': 'Geregeld door je admin',
|
||||
'assistant.connect.disconnect': 'Ontkoppelen',
|
||||
'assistant.connect.disconnectConfirm': '{provider} ontkoppelen? Je chats blijven bewaard.',
|
||||
'assistant.connect.signOut': 'Uitloggen',
|
||||
'assistant.connect.statusOk': 'Verbonden',
|
||||
'assistant.connect.statusNeedsReauth': 'Login verlopen',
|
||||
'assistant.connect.statusSignedOut': 'Uitgelogd',
|
||||
'assistant.connect.statusError': 'Niet bereikbaar',
|
||||
'assistant.connect.quota': '{used} van {count} berichten vandaag gebruikt',
|
||||
'assistant.connect.quota.one': '{used} van {count} bericht vandaag gebruikt',
|
||||
'assistant.connect.claudeLink': 'Gebruik je Claude? Koppel in Claude',
|
||||
'assistant.privacy.what': 'Wat wordt er gedeeld?',
|
||||
'assistant.privacy.banner': 'Je berichten en de reisgegevens die de assistent leest, gaan naar {provider}.',
|
||||
'assistant.privacy.ok': 'Begrepen',
|
||||
'assistant.privacy.shared':
|
||||
'Je berichten en de reisgegevens die de assistent leest, zoals plekken, dagen en boekingen.',
|
||||
'assistant.privacy.notShared': 'Niet je wachtwoord, je bestanden of je andere reizen.',
|
||||
'assistant.privacy.provider': '{provider} verwerkt dit volgens de eigen privacyvoorwaarden.',
|
||||
'assistant.empty.title': 'Wat gaan we plannen?',
|
||||
'assistant.empty.readOnly': 'Je kunt deze reis alleen bekijken, dus ik beantwoord vragen maar verander niets.',
|
||||
'assistant.suggest.planFirstDays': 'Plan de eerste dagen voor me',
|
||||
'assistant.suggest.mustSee': 'Wat mag ik hier niet missen?',
|
||||
'assistant.suggest.packingList': 'Maak een paklijst voor deze reis',
|
||||
'assistant.suggest.emptyDays': 'Welke dagen zijn nog leeg?',
|
||||
'assistant.suggest.rainPlan': 'Wat kunnen we doen als het regent?',
|
||||
'assistant.suggest.today': 'Wat staat er vandaag op het programma?',
|
||||
'assistant.suggest.eatNearby': 'Zoek een plek in de buurt om te eten',
|
||||
'assistant.suggest.weather': 'Hoe ziet het weer eruit?',
|
||||
'assistant.suggest.openTodos': 'Wat staat er nog open op de takenlijst?',
|
||||
'assistant.suggest.settleUp': 'Wie is wie wat verschuldigd?',
|
||||
'assistant.suggest.recap': 'Vat onze reis samen',
|
||||
'assistant.suggest.nearPlace': 'Wat is er nog meer in de buurt van {place}?',
|
||||
'assistant.suggest.placeTips': 'Tips voor een bezoek aan {place}',
|
||||
'assistant.suggest.planDay': 'Plan {day} voor me',
|
||||
'assistant.suggest.dayTooFull': 'Is {day} te vol?',
|
||||
'assistant.thread.scrollDown': 'Naar het nieuwste bericht',
|
||||
'assistant.attach.button': 'Boeking bijvoegen',
|
||||
'assistant.attach.drop': 'Laat hier los, dan lees ik de boeking',
|
||||
'assistant.attach.unsupported': 'Dit bestandstype kan ik niet lezen. Probeer een pdf, e-mail, afbeelding of wallet-pas.',
|
||||
'assistant.attach.tooLarge': 'Dit bestand is groter dan 10 MB.',
|
||||
'assistant.composer.placeholder': 'Vraag alles over {trip}',
|
||||
'assistant.composer.send': 'Versturen',
|
||||
'assistant.composer.stop': 'Stoppen',
|
||||
'assistant.composer.decideFirst': 'Pas eerst de wijzigingen hierboven toe of verwerp ze.',
|
||||
'assistant.context.ask': 'Vragen',
|
||||
'assistant.context.remove': 'Context verwijderen',
|
||||
'assistant.step.reading': 'Je plan lezen',
|
||||
'assistant.step.searching': 'Plekken zoeken',
|
||||
'assistant.step.details': 'Details opzoeken',
|
||||
'assistant.step.nearby': 'In de buurt zoeken',
|
||||
'assistant.step.categories': 'Categorieën controleren',
|
||||
'assistant.step.weather': 'Het weer controleren',
|
||||
'assistant.step.todos': 'De takenlijst lezen',
|
||||
'assistant.step.costs': 'De kosten controleren',
|
||||
'assistant.step.help': 'In de help opzoeken',
|
||||
'assistant.step.readingBooking': 'Boeking lezen',
|
||||
'assistant.step.working': 'Bezig',
|
||||
'assistant.step.collapsed': '{count} stappen',
|
||||
'assistant.step.collapsed.one': '{count} stap',
|
||||
'assistant.change.title': 'Voorgestelde wijzigingen',
|
||||
'assistant.change.doneTitle': 'Klaar',
|
||||
'assistant.change.apply': 'Toepassen',
|
||||
'assistant.change.applyN': '{count} toepassen',
|
||||
'assistant.change.applyN.one': '{count} toepassen',
|
||||
'assistant.change.discard': 'Verwerpen',
|
||||
'assistant.change.include': 'Deze wijziging meenemen',
|
||||
'assistant.change.applied': 'Toegepast',
|
||||
'assistant.change.discarded': 'Verworpen',
|
||||
'assistant.change.failed': 'Kon dit niet toepassen.',
|
||||
'assistant.change.applying': 'Toepassen',
|
||||
'assistant.change.expired': 'Niet meer beschikbaar',
|
||||
'assistant.change.undo': 'Ongedaan maken',
|
||||
'assistant.change.undone': 'Ongedaan gemaakt',
|
||||
'assistant.change.undoStale': 'Intussen gewijzigd. Pas het met de hand aan.',
|
||||
'assistant.change.undoFailed': 'Kon dit niet ongedaan maken.',
|
||||
'assistant.change.showOnMap': 'Op kaart tonen',
|
||||
'assistant.change.empty': 'leeg',
|
||||
'assistant.change.pending': '{count} wijzigingen wachten op je',
|
||||
'assistant.change.pending.one': '{count} wijziging wacht op je',
|
||||
'assistant.change.verb.addPlace': 'Plek toevoegen',
|
||||
'assistant.change.verb.addPlaceToDay': 'Plek aan dag toevoegen',
|
||||
'assistant.change.verb.updatePlace': 'Plek wijzigen',
|
||||
'assistant.change.verb.assignPlace': 'Plek op een dag plannen',
|
||||
'assistant.change.verb.moveAssignment': 'Naar een andere dag verplaatsen',
|
||||
'assistant.change.verb.removeFromDay': 'Van de dag halen',
|
||||
'assistant.change.verb.addDayNote': 'Notitie toevoegen',
|
||||
'assistant.change.verb.updateDayNote': 'Notitie wijzigen',
|
||||
'assistant.change.verb.addPackingItem': 'Aan paklijst toevoegen',
|
||||
'assistant.change.verb.updatePackingItem': 'Pakitem wijzigen',
|
||||
'assistant.change.verb.addTodo': 'Taak toevoegen',
|
||||
'assistant.change.verb.addBudgetItem': 'Uitgave toevoegen',
|
||||
'assistant.change.verb.addReservation': 'Boeking toevoegen',
|
||||
'assistant.change.verb.importBooking': 'Boeking uit bestand toevoegen',
|
||||
'assistant.change.label.name': 'Naam',
|
||||
'assistant.change.label.description': 'Beschrijving',
|
||||
'assistant.change.label.address': 'Adres',
|
||||
'assistant.change.label.coordinates': 'Locatie op kaart',
|
||||
'assistant.change.label.category': 'Categorie',
|
||||
'assistant.change.label.day': 'Dag',
|
||||
'assistant.change.label.time': 'Tijd',
|
||||
'assistant.change.label.endTime': 'Tot',
|
||||
'assistant.change.label.notes': 'Notities',
|
||||
'assistant.change.label.website': 'Website', // same-as-en
|
||||
'assistant.change.label.phone': 'Telefoon',
|
||||
'assistant.change.label.email': 'E-mail',
|
||||
'assistant.change.label.openingHours': 'Openingstijden',
|
||||
'assistant.change.label.price': 'Prijs',
|
||||
'assistant.change.label.currency': 'Valuta',
|
||||
'assistant.change.label.stopType': 'Soort stop',
|
||||
'assistant.change.label.text': 'Tekst',
|
||||
'assistant.change.label.icon': 'Pictogram',
|
||||
'assistant.change.label.color': 'Kleur',
|
||||
'assistant.change.label.position': 'Positie',
|
||||
'assistant.change.label.quantity': 'Aantal',
|
||||
'assistant.change.label.weight': 'Gewicht',
|
||||
'assistant.change.label.bag': 'Tas',
|
||||
'assistant.change.label.packed': 'Ingepakt',
|
||||
'assistant.change.label.dueDate': 'Deadline',
|
||||
'assistant.change.label.priority': 'Prioriteit',
|
||||
'assistant.change.label.assignee': 'Toegewezen aan',
|
||||
'assistant.change.label.type': 'Soort',
|
||||
'assistant.change.label.location': 'Locatie',
|
||||
'assistant.change.label.link': 'Link', // same-as-en
|
||||
'assistant.change.label.confirmation': 'Bevestigingsnummer',
|
||||
'assistant.change.label.checkIn': 'Inchecken',
|
||||
'assistant.change.label.checkOut': 'Uitchecken',
|
||||
'assistant.change.label.stay': 'Verblijf',
|
||||
'assistant.change.label.members': 'Verdeeld over',
|
||||
'assistant.change.label.route': 'Traject',
|
||||
'assistant.history.open': 'Recente chats',
|
||||
'assistant.history.new': 'Nieuwe chat',
|
||||
'assistant.history.empty': 'Nog geen chats.',
|
||||
'assistant.history.retention': 'Chats worden na {count} dagen verwijderd.',
|
||||
'assistant.history.retention.one': 'Chats worden na {count} dag verwijderd.',
|
||||
'assistant.history.forever': 'Chats blijven bewaard tot je ze verwijdert.',
|
||||
'assistant.error.notConnected': 'De assistent is nog niet gekoppeld.',
|
||||
'assistant.error.busy': 'Draait al in een ander venster.',
|
||||
'assistant.error.dailyLimit': 'Dat was het voor vandaag. Tot morgen.',
|
||||
'assistant.error.rateLimited': 'Iets rustiger aan. Probeer het over een minuut opnieuw.',
|
||||
'assistant.error.needsReauth': 'Je login bij {provider} is verlopen.',
|
||||
'assistant.error.usageLimit': 'Je ChatGPT-gebruik is op.',
|
||||
'assistant.error.notEligible': 'Dit ChatGPT-account kan hier niet worden gebruikt.',
|
||||
'assistant.error.invalidKey': 'Je sleutel voor {provider} werkt niet meer.',
|
||||
'assistant.error.providerDown': '{provider} reageert op dit moment niet.',
|
||||
'assistant.error.maxSteps': 'Dat was veel tegelijk. Zal ik doorgaan?',
|
||||
'assistant.error.offline': 'Offline. De assistent heeft internet nodig.',
|
||||
'assistant.error.noBookingsFound': 'Geen boeking gevonden in dit bestand.',
|
||||
'assistant.error.importUnavailable': 'Boekingsbestanden lezen is hier niet beschikbaar.',
|
||||
'assistant.error.generic': 'Er ging iets mis.',
|
||||
'assistant.errorAction.retry': 'Opnieuw proberen',
|
||||
'assistant.errorAction.continueHere': 'Hier verdergaan',
|
||||
'assistant.errorAction.continue': 'Doorgaan',
|
||||
'assistant.errorAction.signIn': 'Opnieuw inloggen',
|
||||
'assistant.errorAction.viewUsage': 'Gebruik bekijken',
|
||||
'assistant.errorAction.otherProvider': 'Andere aanbieder kiezen',
|
||||
'assistant.errorAction.replaceKey': 'Sleutel vervangen',
|
||||
'assistant.errorAction.connectOwn': 'Eigen aanbieder koppelen',
|
||||
'assistant.admin.policy': 'Wie betaalt',
|
||||
'assistant.admin.policy.instance': 'Ik betaal voor iedereen',
|
||||
'assistant.admin.policy.instanceOrUser': 'Ik betaal, eigen toegestaan',
|
||||
'assistant.admin.policy.user': 'Iedereen gebruikt een eigen account',
|
||||
'assistant.admin.policy.instanceHint': 'Iedereen gebruikt de koppeling hieronder en kan geen eigen toevoegen.',
|
||||
'assistant.admin.policy.instanceOrUserHint':
|
||||
'Iedereen gebruikt de koppeling hieronder, tenzij iemand een eigen koppelt.',
|
||||
'assistant.admin.policy.userHint': 'Iedereen koppelt een eigen aanbieder. Jij betaalt niets.',
|
||||
'assistant.admin.connection': 'Koppeling voor iedereen',
|
||||
'assistant.admin.connectionNone': 'Nog geen koppeling.',
|
||||
'assistant.admin.connectionRemove': 'Koppeling verwijderen',
|
||||
'assistant.admin.custom': 'Eigen server',
|
||||
'assistant.admin.customHint': 'Elke OpenAI-compatibele server, bijvoorbeeld Ollama.',
|
||||
'assistant.admin.baseUrl': 'Serveradres',
|
||||
'assistant.admin.apiKeyOptional': 'API-sleutel (optioneel)',
|
||||
'assistant.admin.prefilled': 'Overgenomen uit AI-analyse.',
|
||||
'assistant.admin.dailyLimit': 'Berichten per persoon per dag',
|
||||
'assistant.admin.dailyLimitHint': 'Telt alleen zolang jij betaalt.',
|
||||
'assistant.admin.retention': 'Chats bewaren',
|
||||
'assistant.admin.retentionDays': '{count} dagen',
|
||||
'assistant.admin.retentionDays.one': '{count} dag',
|
||||
'assistant.admin.retentionForever': 'Voor altijd',
|
||||
'assistant.claude.title': 'Koppelen met Claude',
|
||||
'assistant.claude.description': 'Gebruik TREK direct in Claude: voeg het toe als connector, log in, klaar.',
|
||||
'assistant.claude.open': 'Openen in Claude',
|
||||
'assistant.claude.scan': 'Of scan het met je telefoon',
|
||||
'assistant.claude.copyUrl': 'Adres kopiëren',
|
||||
'assistant.claude.qrAlt': 'QR-code om TREK toe te voegen in Claude',
|
||||
'assistant.claude.notPublic': 'Claude kan TREK alleen bereiken als het via https op internet beschikbaar is.',
|
||||
'assistant.mobile.tile': 'Vraag het TREK',
|
||||
'assistant.mobile.tileHint': 'Plan met de assistent',
|
||||
'assistant.mobile.backToChat': 'Terug naar chat',
|
||||
'assistant.step.nearbyAt': 'Zoekt in de buurt van {place}',
|
||||
'assistant.step.searchingFor': 'Zoekt naar {query}',
|
||||
'assistant.step.weatherAt': 'Bekijkt het weer in {place}',
|
||||
'assistant.change.timeline.show': 'Planning tonen',
|
||||
'assistant.change.timeline.hide': 'Planning verbergen',
|
||||
'assistant.change.timeline.new': 'Nieuw',
|
||||
'assistant.change.timeline.travel': '≈ {count} min onderweg',
|
||||
'assistant.change.timeline.travel.one': '≈ {count} min onderweg',
|
||||
'assistant.change.timeline.gap': '{count} min vrij',
|
||||
'assistant.change.timeline.gap.one': '{count} min vrij',
|
||||
'assistant.change.timeline.overlap': 'Overlapt met {place}',
|
||||
'assistant.change.timeline.moreDays': 'Nog {count} dagen',
|
||||
'assistant.change.timeline.moreDays.one': 'Nog {count} dag',
|
||||
'assistant.change.viewOnMap': 'Bekijk op de kaart',
|
||||
'assistant.change.before': 'Voor',
|
||||
'assistant.change.after': 'Na',
|
||||
'assistant.change.compare': 'Vergelijk op de kaart',
|
||||
'assistant.change.undoAll': 'Alles ongedaan maken',
|
||||
'assistant.change.undoneAll': '{count} wijzigingen ongedaan gemaakt',
|
||||
'assistant.change.undoneAll.one': '{count} wijziging ongedaan gemaakt',
|
||||
'assistant.change.undoAllPartial': 'Ongedaan: {done}; intussen gewijzigd: {stale}',
|
||||
'assistant.change.undoAllFailed': '{count} wijzigingen konden niet ongedaan worden gemaakt',
|
||||
'assistant.change.undoAllFailed.one': '{count} wijziging kon niet ongedaan worden gemaakt',
|
||||
'assistant.place.strip': 'Gevonden plekken',
|
||||
'assistant.place.addTo': 'Toevoegen aan {day}',
|
||||
'assistant.place.addToShort': '+ Dag {n}',
|
||||
'assistant.place.pickDay': 'Kies een dag',
|
||||
'assistant.place.suggested': 'Voorgesteld',
|
||||
'assistant.place.inTrip': 'Al in je reis',
|
||||
'assistant.place.showOnMap': 'Op de kaart',
|
||||
'assistant.place.openNow': 'Nu open',
|
||||
'assistant.place.closed': 'Gesloten',
|
||||
'assistant.place.fromPrev': '{distance} van de vorige stop',
|
||||
'assistant.place.more': 'Nog {count}',
|
||||
'assistant.place.more.one': 'Nog {count}',
|
||||
'assistant.mention.days': 'Dagen',
|
||||
'assistant.mention.places': 'Plekken',
|
||||
'assistant.mention.bookings': 'Boekingen',
|
||||
'assistant.mention.none': 'Niets gevonden',
|
||||
'assistant.mention.hint': 'Typ @ om dagen, plekken en boekingen te noemen.',
|
||||
'assistant.attach.dropPlace': 'Zet de plek hier neer',
|
||||
'assistant.weather.strip': 'Weer voor {count} dagen',
|
||||
'assistant.weather.strip.one': 'Weer voor {count} dag',
|
||||
'assistant.weather.rain': '{pct}% regen',
|
||||
'assistant.settings.followMap': 'Kaart volgt de assistent',
|
||||
'assistant.settings.followMapHint': 'De kaart beweegt mee naar waar de assistent zoekt en plekken voorstelt.',
|
||||
'assistant.error.decideFirst': 'Beslis eerst over de openstaande voorstellen.',
|
||||
'assistant.error.providerLimit': 'Limiet bereikt bij {provider}. Probeer het later opnieuw.',
|
||||
'assistant.hint.title': 'Even kijken',
|
||||
'assistant.hint.dismiss': 'Deze tip verbergen',
|
||||
'assistant.hint.more': 'Nog {count}',
|
||||
'assistant.hint.more.one': 'Nog {count}',
|
||||
'assistant.hint.overlap': '{a} en {b} overlappen op {day}',
|
||||
'assistant.hint.overlap.prompt': 'Op {day} overlappen {a} en {b}. Hoe lossen we dat op?',
|
||||
'assistant.hint.closed': '{place} is op {day} gesloten',
|
||||
'assistant.hint.closed.prompt': '{place} is op {day} gesloten. Wat doen we in plaats daarvan?',
|
||||
'assistant.hint.opensLate': '{place} gaat op {day} pas om {time} open',
|
||||
'assistant.hint.closesEarly': '{place} sluit op {day} al om {time}',
|
||||
'assistant.hint.outsideHours.prompt':
|
||||
'Ons bezoek aan {place} op {day} past niet bij de openingstijden. Hoe verschuiven we het het best?',
|
||||
'assistant.hint.gap': '{day}: {count} uur vrij vanaf {from}',
|
||||
'assistant.hint.gap.one': '{day}: {count} uur vrij vanaf {from}',
|
||||
'assistant.hint.gap.prompt': 'Op {day} staat er van {from} tot {to} niets gepland. Wat past daar?',
|
||||
'assistant.hint.longLeg': '{from} en {to} liggen hemelsbreed {distance} uit elkaar',
|
||||
'assistant.hint.longLeg.prompt': 'Is het op {day} te ver van {from} naar {to}? Hoe komen we er het best?',
|
||||
'assistant.hint.tightLeg': 'Maar {count} minuten tussen {from} en {to}',
|
||||
'assistant.hint.tightLeg.one': 'Maar {count} minuut tussen {from} en {to}',
|
||||
'assistant.hint.tightLeg.prompt':
|
||||
'Is er op {day} genoeg tijd om van {from} naar {to} te gaan? Wat moeten we aanpassen?',
|
||||
'assistant.hint.noStay': 'Nog geen overnachting voor {count} nachten vanaf {day}',
|
||||
'assistant.hint.noStay.one': 'Nog geen overnachting voor {count} nacht vanaf {day}',
|
||||
'assistant.hint.noStay.prompt': 'We hebben vanaf {day} nog een overnachting nodig. Wat stel je voor?',
|
||||
'assistant.hint.unplacedBooking': '{title} staat nog bij geen enkele dag',
|
||||
'assistant.hint.unplacedBookingDay': '{title} hoort waarschijnlijk bij {day}',
|
||||
'assistant.hint.unplacedBookings': '{count} boekingen staan nog bij geen enkele dag',
|
||||
'assistant.hint.unplacedBookings.one': '{count} boeking staat nog bij geen enkele dag',
|
||||
'assistant.hint.unplacedBooking.prompt': 'Welke boekingen staan nog bij geen enkele dag, en waar horen ze?',
|
||||
'assistant.hint.menuOn': 'Tips tonen',
|
||||
'assistant.hint.menuOff': 'Geen tips tonen',
|
||||
'assistant.launcher.withHints': 'Assistent, {count} tips',
|
||||
'assistant.launcher.withHints.one': 'Assistent, {count} tip',
|
||||
'assistant.mobile.tileHints': '{count} punten om te checken in je plan',
|
||||
'assistant.mobile.tileHints.one': '{count} punt om te checken in je plan',
|
||||
'assistant.share.button': 'Delen in de reischat',
|
||||
'assistant.share.title': 'Delen in de reischat',
|
||||
'assistant.share.who': 'Iedereen in deze reis ziet dit bericht, gemarkeerd als van de assistent.',
|
||||
'assistant.share.includeChanges': 'Toegepaste wijzigingen toevoegen',
|
||||
'assistant.share.changesHeading': 'Toegepast:',
|
||||
'assistant.share.truncated': 'Ingekort tot {count} tekens',
|
||||
'assistant.share.truncated.one': 'Ingekort tot {count} teken',
|
||||
'assistant.share.send': 'Delen',
|
||||
'assistant.share.done': 'Gedeeld in de reischat',
|
||||
'assistant.share.shared': 'Gedeeld',
|
||||
'assistant.share.failed': 'Delen is niet gelukt. Probeer het zo nog eens.',
|
||||
'assistant.share.chatOff': 'De reischat staat uit.',
|
||||
};
|
||||
export default assistant;
|
||||
@@ -106,5 +106,7 @@ const collab: TranslationStrings = {
|
||||
'collab.chat.attachImages': 'Afbeeldingen toevoegen',
|
||||
'collab.chat.attachedImage': 'Bijgevoegde afbeelding',
|
||||
'collab.chat.closeImage': 'Afbeelding sluiten',
|
||||
'collab.chat.fromAssistant': 'van de assistent',
|
||||
'collab.chat.fromAssistantHint': '{name} heeft dit antwoord van de assistent gedeeld',
|
||||
};
|
||||
export default collab;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import admin from './admin';
|
||||
import airport from './airport';
|
||||
import assistant from './assistant';
|
||||
import atlas from './atlas';
|
||||
import backup from './backup';
|
||||
import budget from './budget';
|
||||
@@ -116,5 +117,6 @@ const locale = {
|
||||
...dawarich,
|
||||
...docsync,
|
||||
...ferratas,
|
||||
...assistant,
|
||||
};
|
||||
export default locale;
|
||||
|
||||
@@ -85,6 +85,7 @@ const oauth: TranslationStrings = {
|
||||
'oauth.authorize.loginButton': 'Sign in to TREK', // en-fallback
|
||||
'oauth.authorize.requestLabel': 'Authorization Request', // en-fallback
|
||||
'oauth.authorize.requestDescription': 'This application is requesting access to your TREK account.', // en-fallback
|
||||
'oauth.authorize.verifiedHost': 'Geverifieerd domein: {host}',
|
||||
'oauth.authorize.trustNote': 'Only grant access to applications you trust. Your data stays on your server.', // en-fallback
|
||||
'oauth.authorize.selectScope': 'Select at least one scope', // en-fallback
|
||||
'oauth.authorize.approveScopes': 'Goedkeuren ({count} machtigingen)',
|
||||
|
||||
@@ -657,6 +657,9 @@ const admin: TranslationStrings = {
|
||||
'admin.addons.catalog.llm_parsing.name': 'Analiza AI',
|
||||
'admin.addons.catalog.llm_parsing.description':
|
||||
'Odczytuje rezerwacje, których nie rozumie wbudowany parser, przy użyciu wybranego modelu AI',
|
||||
'admin.addons.catalog.ai_assistant.name': 'Asystent AI',
|
||||
'admin.addons.catalog.ai_assistant.description':
|
||||
'Czat w każdej podróży, który odpowiada na pytania i proponuje zmiany do Twojego zatwierdzenia',
|
||||
'admin.addons.llm.vision.auto': 'Automatycznie',
|
||||
'admin.addons.llm.vision.on': 'Tak',
|
||||
'admin.addons.llm.vision.off': 'Nie',
|
||||
|
||||
@@ -0,0 +1,384 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistant: TranslationStrings = {
|
||||
'assistant.launcher.tooltip': 'Asystent',
|
||||
'assistant.launcher.ctrlKey': 'Ctrl', // same-as-en
|
||||
'assistant.picker.title': 'Która podróż?',
|
||||
'assistant.picker.empty': 'Najpierw utwórz podróż, a potem asystent pomoże Ci ją zaplanować.',
|
||||
'assistant.picker.now': 'Teraz',
|
||||
'assistant.picker.next': 'Następna',
|
||||
'assistant.window.title': 'Asystent',
|
||||
'assistant.window.log': 'Rozmowa z asystentem',
|
||||
'assistant.window.menu': 'Więcej opcji',
|
||||
'assistant.window.settings': 'Ustawienia AI',
|
||||
'assistant.window.deleteThread': 'Usuń czat',
|
||||
'assistant.window.deleteConfirm': 'Usunąć ten czat? Tej operacji nie można cofnąć.',
|
||||
'assistant.setup.question': 'Z czym chcesz rozmawiać?',
|
||||
'assistant.setup.beta': 'Beta', // same-as-en
|
||||
'assistant.setup.chatgptNote': 'Oficjalną drogą jest klucz API. Logowanie przez ChatGPT jest wciąż w wersji beta.',
|
||||
'assistant.setup.keySubtitle': 'Anthropic, OpenAI, Gemini lub OpenRouter',
|
||||
'assistant.setup.adminOnly': 'Konfiguruje to Twój administrator.',
|
||||
'assistant.setup.forEveryone': 'Skonfiguruj dla wszystkich',
|
||||
'assistant.connect.title': 'Asystent AI',
|
||||
'assistant.connect.description':
|
||||
'Rozmawiaj z asystentem, który zna Twoją podróż i proponuje zmiany do Twojego zatwierdzenia.',
|
||||
'assistant.connect.chatgpt': 'Zaloguj się przez ChatGPT',
|
||||
'assistant.connect.chatgptHint': 'Korzysta z Twojego planu ChatGPT. Klucz nie jest potrzebny.',
|
||||
'assistant.connect.chatgptStep1': 'Zaloguj się do ChatGPT w nowej karcie.',
|
||||
'assistant.connect.chatgptOpen': 'Otwórz logowanie do ChatGPT',
|
||||
'assistant.connect.chatgptStep2':
|
||||
'Potem przeglądarka pokaże stronę błędu. Tak ma być. Skopiuj adres z góry i wróć tutaj.',
|
||||
'assistant.connect.chatgptPaste': 'Wklej adres',
|
||||
'assistant.connect.chatgptWrong': 'To nie jest właściwy adres. Zaczyna się od 127.0.0.1.',
|
||||
'assistant.connect.chatgptExpired': 'Trwało to zbyt długo. Zaloguj się ponownie.',
|
||||
'assistant.connect.chatgptOtherAccount': 'To inne konto ChatGPT. Najpierw usuń połączenie.',
|
||||
'assistant.connect.key': 'Wklej klucz API',
|
||||
'assistant.connect.keyPlaceholder': 'Wklej tutaj swój klucz',
|
||||
'assistant.connect.detected': 'Wykryto: {provider}',
|
||||
'assistant.connect.whichProvider': 'Od którego dostawcy jest ten klucz?',
|
||||
'assistant.connect.testing': 'Sprawdzanie połączenia',
|
||||
'assistant.connect.ok': 'Połączono. Zaczynamy.',
|
||||
'assistant.connect.openAssistant': 'Otwórz asystenta',
|
||||
'assistant.connect.badKey': 'Ten klucz nie działa.',
|
||||
'assistant.connect.unknownModel': 'Ten model nie jest tu dostępny.',
|
||||
'assistant.connect.getKey': 'Uzyskaj klucz',
|
||||
'assistant.connect.hintAnthropic': 'Claude Max i Team zawierają środki na API.',
|
||||
'assistant.connect.hintOpenai': 'Utwórz klucz w panelu OpenAI.',
|
||||
'assistant.connect.hintGemini': 'AI Studio ma darmowy poziom z limitami na minutę i na dzień, w którym Google może używać tego, co wysyłasz, do ulepszania swoich produktów. Po włączeniu rozliczeń limity są wyższe i to nie obowiązuje.',
|
||||
'assistant.connect.hintOpenrouter': 'Zaloguj się przez OpenRouter, bez kopiowania klucza.',
|
||||
'assistant.connect.openrouter': 'Połącz OpenRouter',
|
||||
'assistant.connect.save': 'Połącz',
|
||||
'assistant.connect.model': 'Model', // same-as-en
|
||||
'assistant.connect.modelHint': 'Na liście są tylko modele, które potrafią używać narzędzi.',
|
||||
'assistant.connect.free': 'Dostępny darmowy poziom',
|
||||
'assistant.connect.paid': 'Płatność za użycie',
|
||||
'assistant.model.recommended': 'Zalecany',
|
||||
'assistant.model.free': 'Za darmo',
|
||||
'assistant.connect.connected': 'Połączono z {provider}',
|
||||
'assistant.connect.providedByAdmin': 'Zapewnia Twój administrator',
|
||||
'assistant.connect.disconnect': 'Rozłącz',
|
||||
'assistant.connect.disconnectConfirm': 'Rozłączyć {provider}? Twoje czaty zostaną.',
|
||||
'assistant.connect.signOut': 'Wyloguj się',
|
||||
'assistant.connect.statusOk': 'Połączono',
|
||||
'assistant.connect.statusNeedsReauth': 'Sesja wygasła',
|
||||
'assistant.connect.statusSignedOut': 'Wylogowano',
|
||||
'assistant.connect.statusError': 'Brak połączenia',
|
||||
'assistant.connect.quota': 'Dziś wykorzystano {used} z {count} wiadomości',
|
||||
'assistant.connect.quota.one': 'Dziś wykorzystano {used} z {count} wiadomości',
|
||||
'assistant.connect.quota.few': 'Dziś wykorzystano {used} z {count} wiadomości',
|
||||
'assistant.connect.quota.many': 'Dziś wykorzystano {used} z {count} wiadomości',
|
||||
'assistant.connect.claudeLink': 'Używasz Claude? Połącz w Claude',
|
||||
'assistant.privacy.what': 'Co jest udostępniane?',
|
||||
'assistant.privacy.banner': 'Twoje wiadomości i dane podróży, które czyta asystent, trafiają do {provider}.',
|
||||
'assistant.privacy.ok': 'Rozumiem',
|
||||
'assistant.privacy.shared':
|
||||
'Twoje wiadomości i szczegóły podróży, które czyta asystent, np. miejsca, dni i rezerwacje.',
|
||||
'assistant.privacy.notShared': 'Nie Twoje hasło, pliki ani inne podróże.',
|
||||
'assistant.privacy.provider': '{provider} przetwarza je zgodnie z własną polityką prywatności.',
|
||||
'assistant.empty.title': 'Co planujemy?',
|
||||
'assistant.empty.readOnly': 'Możesz tylko przeglądać tę podróż, więc odpowiem na pytania, ale niczego nie zmienię.',
|
||||
'assistant.suggest.planFirstDays': 'Zaplanuj mi pierwsze dni',
|
||||
'assistant.suggest.mustSee': 'Czego nie mogę tu przegapić?',
|
||||
'assistant.suggest.packingList': 'Zrób listę rzeczy do spakowania na tę podróż',
|
||||
'assistant.suggest.emptyDays': 'Które dni są jeszcze puste?',
|
||||
'assistant.suggest.rainPlan': 'Co możemy robić, jeśli będzie padać?',
|
||||
'assistant.suggest.today': 'Co jest dziś w planie?',
|
||||
'assistant.suggest.eatNearby': 'Znajdź miejsce, gdzie można zjeść w pobliżu',
|
||||
'assistant.suggest.weather': 'Jaka będzie pogoda?',
|
||||
'assistant.suggest.openTodos': 'Co zostało na liście zadań?',
|
||||
'assistant.suggest.settleUp': 'Kto komu ile jest winien?',
|
||||
'assistant.suggest.recap': 'Podsumuj naszą podróż',
|
||||
'assistant.suggest.nearPlace': 'Co jeszcze jest w pobliżu {place}?',
|
||||
'assistant.suggest.placeTips': 'Wskazówki na wizytę: {place}',
|
||||
'assistant.suggest.planDay': 'Zaplanuj mi {day}',
|
||||
'assistant.suggest.dayTooFull': 'Czy {day} jest zbyt napięty?',
|
||||
'assistant.thread.scrollDown': 'Przejdź do najnowszej wiadomości',
|
||||
'assistant.attach.button': 'Dołącz rezerwację',
|
||||
'assistant.attach.drop': 'Upuść tutaj, a odczytam rezerwację',
|
||||
'assistant.attach.unsupported': 'Nie mogę odczytać tego typu pliku. Spróbuj PDF, e-mail, obraz lub kartę Wallet.',
|
||||
'assistant.attach.tooLarge': 'Ten plik jest większy niż 10 MB.',
|
||||
'assistant.composer.placeholder': 'Zapytaj o cokolwiek: {trip}',
|
||||
'assistant.composer.send': 'Wyślij',
|
||||
'assistant.composer.stop': 'Zatrzymaj',
|
||||
'assistant.composer.decideFirst': 'Najpierw zastosuj lub odrzuć zmiany powyżej.',
|
||||
'assistant.context.ask': 'Zapytaj',
|
||||
'assistant.context.remove': 'Usuń kontekst',
|
||||
'assistant.step.reading': 'Czytam Twój plan',
|
||||
'assistant.step.searching': 'Szukam miejsc',
|
||||
'assistant.step.details': 'Sprawdzam szczegóły',
|
||||
'assistant.step.nearby': 'Szukam w pobliżu',
|
||||
'assistant.step.categories': 'Sprawdzam kategorie',
|
||||
'assistant.step.weather': 'Sprawdzam pogodę',
|
||||
'assistant.step.todos': 'Czytam listę zadań',
|
||||
'assistant.step.costs': 'Sprawdzam koszty',
|
||||
'assistant.step.help': 'Szukam w pomocy',
|
||||
'assistant.step.readingBooking': 'Czytam rezerwację',
|
||||
'assistant.step.working': 'Pracuję nad tym',
|
||||
'assistant.step.collapsed': '{count} kroku',
|
||||
'assistant.step.collapsed.one': '{count} krok',
|
||||
'assistant.step.collapsed.few': '{count} kroki',
|
||||
'assistant.step.collapsed.many': '{count} kroków',
|
||||
'assistant.change.title': 'Proponowane zmiany',
|
||||
'assistant.change.doneTitle': 'Gotowe',
|
||||
'assistant.change.apply': 'Zastosuj',
|
||||
'assistant.change.applyN': 'Zastosuj {count}',
|
||||
'assistant.change.applyN.one': 'Zastosuj {count}',
|
||||
'assistant.change.applyN.few': 'Zastosuj {count}',
|
||||
'assistant.change.applyN.many': 'Zastosuj {count}',
|
||||
'assistant.change.discard': 'Odrzuć',
|
||||
'assistant.change.include': 'Uwzględnij tę zmianę',
|
||||
'assistant.change.applied': 'Zastosowano',
|
||||
'assistant.change.discarded': 'Odrzucono',
|
||||
'assistant.change.failed': 'Nie udało się tego zastosować.',
|
||||
'assistant.change.applying': 'Stosowanie',
|
||||
'assistant.change.expired': 'Już niedostępne',
|
||||
'assistant.change.undo': 'Cofnij',
|
||||
'assistant.change.undone': 'Cofnięto',
|
||||
'assistant.change.undoStale': 'W międzyczasie coś się zmieniło. Popraw to ręcznie.',
|
||||
'assistant.change.undoFailed': 'Nie udało się tego cofnąć.',
|
||||
'assistant.change.showOnMap': 'Pokaż na mapie',
|
||||
'assistant.change.empty': 'puste',
|
||||
'assistant.change.pending': '{count} zmiany czeka na Ciebie',
|
||||
'assistant.change.pending.one': '{count} zmiana czeka na Ciebie',
|
||||
'assistant.change.pending.few': '{count} zmiany czekają na Ciebie',
|
||||
'assistant.change.pending.many': '{count} zmian czeka na Ciebie',
|
||||
'assistant.change.verb.addPlace': 'Dodaj miejsce',
|
||||
'assistant.change.verb.addPlaceToDay': 'Dodaj miejsce do dnia',
|
||||
'assistant.change.verb.updatePlace': 'Zmień miejsce',
|
||||
'assistant.change.verb.assignPlace': 'Zaplanuj miejsce na dzień',
|
||||
'assistant.change.verb.moveAssignment': 'Przenieś na inny dzień',
|
||||
'assistant.change.verb.removeFromDay': 'Usuń z dnia',
|
||||
'assistant.change.verb.addDayNote': 'Dodaj notatkę',
|
||||
'assistant.change.verb.updateDayNote': 'Zmień notatkę',
|
||||
'assistant.change.verb.addPackingItem': 'Dodaj do listy pakowania',
|
||||
'assistant.change.verb.updatePackingItem': 'Zmień rzecz do spakowania',
|
||||
'assistant.change.verb.addTodo': 'Dodaj zadanie',
|
||||
'assistant.change.verb.addBudgetItem': 'Dodaj wydatek',
|
||||
'assistant.change.verb.addReservation': 'Dodaj rezerwację',
|
||||
'assistant.change.verb.importBooking': 'Dodaj rezerwację z pliku',
|
||||
'assistant.change.label.name': 'Nazwa',
|
||||
'assistant.change.label.description': 'Opis',
|
||||
'assistant.change.label.address': 'Adres',
|
||||
'assistant.change.label.coordinates': 'Położenie na mapie',
|
||||
'assistant.change.label.category': 'Kategoria',
|
||||
'assistant.change.label.day': 'Dzień',
|
||||
'assistant.change.label.time': 'Godzina',
|
||||
'assistant.change.label.endTime': 'Do',
|
||||
'assistant.change.label.notes': 'Notatki',
|
||||
'assistant.change.label.website': 'Strona internetowa',
|
||||
'assistant.change.label.phone': 'Telefon',
|
||||
'assistant.change.label.email': 'E-mail',
|
||||
'assistant.change.label.openingHours': 'Godziny otwarcia',
|
||||
'assistant.change.label.price': 'Cena',
|
||||
'assistant.change.label.currency': 'Waluta',
|
||||
'assistant.change.label.stopType': 'Rodzaj przystanku',
|
||||
'assistant.change.label.text': 'Tekst',
|
||||
'assistant.change.label.icon': 'Ikona',
|
||||
'assistant.change.label.color': 'Kolor',
|
||||
'assistant.change.label.position': 'Pozycja',
|
||||
'assistant.change.label.quantity': 'Ilość',
|
||||
'assistant.change.label.weight': 'Waga',
|
||||
'assistant.change.label.bag': 'Torba',
|
||||
'assistant.change.label.packed': 'Spakowane',
|
||||
'assistant.change.label.dueDate': 'Termin',
|
||||
'assistant.change.label.priority': 'Priorytet',
|
||||
'assistant.change.label.assignee': 'Przypisane do',
|
||||
'assistant.change.label.type': 'Typ',
|
||||
'assistant.change.label.location': 'Miejsce',
|
||||
'assistant.change.label.link': 'Link', // same-as-en
|
||||
'assistant.change.label.confirmation': 'Numer potwierdzenia',
|
||||
'assistant.change.label.checkIn': 'Zameldowanie',
|
||||
'assistant.change.label.checkOut': 'Wymeldowanie',
|
||||
'assistant.change.label.stay': 'Pobyt',
|
||||
'assistant.change.label.members': 'Podział między',
|
||||
'assistant.change.label.route': 'Trasa',
|
||||
'assistant.history.open': 'Ostatnie czaty',
|
||||
'assistant.history.new': 'Nowy czat',
|
||||
'assistant.history.empty': 'Brak czatów.',
|
||||
'assistant.history.retention': 'Czaty są usuwane po {count} dnia.',
|
||||
'assistant.history.retention.one': 'Czaty są usuwane po {count} dniu.',
|
||||
'assistant.history.retention.few': 'Czaty są usuwane po {count} dniach.',
|
||||
'assistant.history.retention.many': 'Czaty są usuwane po {count} dniach.',
|
||||
'assistant.history.forever': 'Czaty są przechowywane, dopóki ich nie usuniesz.',
|
||||
'assistant.error.notConnected': 'Asystent nie jest jeszcze połączony.',
|
||||
'assistant.error.busy': 'Działa już w innym oknie.',
|
||||
'assistant.error.dailyLimit': 'Na dziś to wszystko. Do jutra.',
|
||||
'assistant.error.rateLimited': 'Trochę wolniej. Spróbuj ponownie za minutę.',
|
||||
'assistant.error.needsReauth': 'Twoja sesja {provider} wygasła.',
|
||||
'assistant.error.usageLimit': 'Wykorzystano limit ChatGPT.',
|
||||
'assistant.error.notEligible': 'Tego konta ChatGPT nie można tu użyć.',
|
||||
'assistant.error.invalidKey': 'Twój klucz {provider} już nie działa.',
|
||||
'assistant.error.providerDown': '{provider} w tej chwili nie odpowiada.',
|
||||
'assistant.error.maxSteps': 'To było dużo naraz. Kontynuować?',
|
||||
'assistant.error.offline': 'Brak sieci. Asystent potrzebuje internetu.',
|
||||
'assistant.error.noBookingsFound': 'W tym pliku nie znaleziono żadnej rezerwacji.',
|
||||
'assistant.error.importUnavailable': 'Odczyt plików z rezerwacjami nie jest tu dostępny.',
|
||||
'assistant.error.generic': 'Coś poszło nie tak.',
|
||||
'assistant.errorAction.retry': 'Spróbuj ponownie',
|
||||
'assistant.errorAction.continueHere': 'Kontynuuj tutaj',
|
||||
'assistant.errorAction.continue': 'Kontynuuj',
|
||||
'assistant.errorAction.signIn': 'Zaloguj się ponownie',
|
||||
'assistant.errorAction.viewUsage': 'Pokaż wykorzystanie',
|
||||
'assistant.errorAction.otherProvider': 'Wybierz innego dostawcę',
|
||||
'assistant.errorAction.replaceKey': 'Zmień klucz',
|
||||
'assistant.errorAction.connectOwn': 'Połącz własnego dostawcę',
|
||||
'assistant.admin.policy': 'Kto płaci',
|
||||
'assistant.admin.policy.instance': 'Płacę za wszystkich',
|
||||
'assistant.admin.policy.instanceOrUser': 'Płacę, własne dozwolone',
|
||||
'assistant.admin.policy.user': 'Każdy używa własnego konta',
|
||||
'assistant.admin.policy.instanceHint': 'Wszyscy używają poniższego połączenia i nie mogą dodać własnego.',
|
||||
'assistant.admin.policy.instanceOrUserHint': 'Wszyscy używają poniższego połączenia, chyba że połączą własne.',
|
||||
'assistant.admin.policy.userHint': 'Każdy łączy własnego dostawcę. Ty za nic nie płacisz.',
|
||||
'assistant.admin.connection': 'Połączenie dla wszystkich',
|
||||
'assistant.admin.connectionNone': 'Brak połączenia.',
|
||||
'assistant.admin.connectionRemove': 'Usuń połączenie',
|
||||
'assistant.admin.custom': 'Własny serwer',
|
||||
'assistant.admin.customHint': 'Dowolny serwer zgodny z OpenAI, na przykład Ollama.',
|
||||
'assistant.admin.baseUrl': 'Adres serwera',
|
||||
'assistant.admin.apiKeyOptional': 'Klucz API (opcjonalnie)',
|
||||
'assistant.admin.prefilled': 'Przejęte z Analizy AI.',
|
||||
'assistant.admin.dailyLimit': 'Wiadomości na osobę dziennie',
|
||||
'assistant.admin.dailyLimitHint': 'Liczy się tylko wtedy, gdy Ty płacisz.',
|
||||
'assistant.admin.retention': 'Przechowuj czaty',
|
||||
'assistant.admin.retentionDays': '{count} dnia',
|
||||
'assistant.admin.retentionDays.one': '{count} dzień',
|
||||
'assistant.admin.retentionDays.few': '{count} dni',
|
||||
'assistant.admin.retentionDays.many': '{count} dni',
|
||||
'assistant.admin.retentionForever': 'Zawsze',
|
||||
'assistant.claude.title': 'Połącz z Claude',
|
||||
'assistant.claude.description': 'Używaj TREK bezpośrednio w Claude: dodaj jako konektor, zaloguj się, gotowe.',
|
||||
'assistant.claude.open': 'Otwórz w Claude',
|
||||
'assistant.claude.scan': 'Albo zeskanuj telefonem',
|
||||
'assistant.claude.copyUrl': 'Kopiuj adres',
|
||||
'assistant.claude.qrAlt': 'Kod QR do dodania TREK w Claude',
|
||||
'assistant.claude.notPublic':
|
||||
'Claude może połączyć się z TREK tylko wtedy, gdy jest on dostępny przez https w internecie.',
|
||||
'assistant.mobile.tile': 'Zapytaj TREK',
|
||||
'assistant.mobile.tileHint': 'Planuj z asystentem',
|
||||
'assistant.mobile.backToChat': 'Wróć do czatu',
|
||||
'assistant.step.nearbyAt': 'Szuka w pobliżu: {place}',
|
||||
'assistant.step.searchingFor': 'Szuka: {query}',
|
||||
'assistant.step.weatherAt': 'Sprawdza pogodę: {place}',
|
||||
'assistant.change.timeline.show': 'Pokaż plan dnia',
|
||||
'assistant.change.timeline.hide': 'Ukryj plan dnia',
|
||||
'assistant.change.timeline.new': 'Nowe',
|
||||
'assistant.change.timeline.travel': '≈ {count} min drogi',
|
||||
'assistant.change.timeline.travel.one': '≈ {count} min drogi',
|
||||
'assistant.change.timeline.travel.few': '≈ {count} min drogi',
|
||||
'assistant.change.timeline.travel.many': '≈ {count} min drogi',
|
||||
'assistant.change.timeline.gap': '{count} min wolnego',
|
||||
'assistant.change.timeline.gap.one': '{count} min wolnego',
|
||||
'assistant.change.timeline.gap.few': '{count} min wolnego',
|
||||
'assistant.change.timeline.gap.many': '{count} min wolnego',
|
||||
'assistant.change.timeline.overlap': 'Nakłada się na {place}',
|
||||
'assistant.change.timeline.moreDays': 'Jeszcze {count} dnia',
|
||||
'assistant.change.timeline.moreDays.one': 'Jeszcze {count} dzień',
|
||||
'assistant.change.timeline.moreDays.few': 'Jeszcze {count} dni',
|
||||
'assistant.change.timeline.moreDays.many': 'Jeszcze {count} dni',
|
||||
'assistant.change.viewOnMap': 'Zobacz na mapie',
|
||||
'assistant.change.before': 'Przed',
|
||||
'assistant.change.after': 'Po',
|
||||
'assistant.change.compare': 'Porównaj na mapie',
|
||||
'assistant.change.undoAll': 'Cofnij wszystko',
|
||||
'assistant.change.undoneAll': 'Cofnięto zmiany: {count}',
|
||||
'assistant.change.undoneAll.one': 'Cofnięto {count} zmianę',
|
||||
'assistant.change.undoneAll.few': 'Cofnięto {count} zmiany',
|
||||
'assistant.change.undoneAll.many': 'Cofnięto {count} zmian',
|
||||
'assistant.change.undoAllPartial': 'Cofnięte: {done}, zmienione w międzyczasie: {stale}',
|
||||
'assistant.change.undoAllFailed': 'Nie udało się cofnąć zmian: {count}',
|
||||
'assistant.change.undoAllFailed.one': 'Nie udało się cofnąć {count} zmiany',
|
||||
'assistant.change.undoAllFailed.few': 'Nie udało się cofnąć {count} zmian',
|
||||
'assistant.change.undoAllFailed.many': 'Nie udało się cofnąć {count} zmian',
|
||||
'assistant.place.strip': 'Znalezione miejsca',
|
||||
'assistant.place.addTo': 'Dodaj do: {day}',
|
||||
'assistant.place.addToShort': '+ Dzień {n}',
|
||||
'assistant.place.pickDay': 'Wybierz dzień',
|
||||
'assistant.place.suggested': 'Zaproponowano',
|
||||
'assistant.place.inTrip': 'Już w twojej podróży',
|
||||
'assistant.place.showOnMap': 'Na mapie',
|
||||
'assistant.place.openNow': 'Teraz otwarte',
|
||||
'assistant.place.closed': 'Zamknięte',
|
||||
'assistant.place.fromPrev': '{distance} od poprzedniego przystanku',
|
||||
'assistant.place.more': 'Jeszcze {count}',
|
||||
'assistant.place.more.one': 'Jeszcze {count}',
|
||||
'assistant.place.more.few': 'Jeszcze {count}',
|
||||
'assistant.place.more.many': 'Jeszcze {count}',
|
||||
'assistant.mention.days': 'Dni',
|
||||
'assistant.mention.places': 'Miejsca',
|
||||
'assistant.mention.bookings': 'Rezerwacje',
|
||||
'assistant.mention.none': 'Nic nie znaleziono',
|
||||
'assistant.mention.hint': 'Wpisz @, aby wspomnieć dni, miejsca i rezerwacje.',
|
||||
'assistant.attach.dropPlace': 'Upuść miejsce tutaj',
|
||||
'assistant.weather.strip': 'Pogoda na {count} dnia',
|
||||
'assistant.weather.strip.one': 'Pogoda na {count} dzień',
|
||||
'assistant.weather.strip.few': 'Pogoda na {count} dni',
|
||||
'assistant.weather.strip.many': 'Pogoda na {count} dni',
|
||||
'assistant.weather.rain': '{pct}% deszczu',
|
||||
'assistant.settings.followMap': 'Mapa podąża za asystentem',
|
||||
'assistant.settings.followMapHint': 'Mapa przesuwa się tam, gdzie asystent szuka i proponuje miejsca.',
|
||||
'assistant.error.decideFirst': 'Najpierw zdecyduj o otwartych propozycjach.',
|
||||
'assistant.error.providerLimit': 'Osiągnięto limit w {provider}. Spróbuj ponownie później.',
|
||||
'assistant.hint.title': 'Warto zerknąć',
|
||||
'assistant.hint.dismiss': 'Ukryj tę wskazówkę',
|
||||
'assistant.hint.more': 'Jeszcze {count}',
|
||||
'assistant.hint.more.one': 'Jeszcze {count}',
|
||||
'assistant.hint.more.few': 'Jeszcze {count}',
|
||||
'assistant.hint.more.many': 'Jeszcze {count}',
|
||||
'assistant.hint.overlap': '{a} i {b} nakładają się: {day}',
|
||||
'assistant.hint.overlap.prompt': '{day}: {a} i {b} nakładają się. Jak to rozwiążemy?',
|
||||
'assistant.hint.closed': '{place} jest zamknięte: {day}',
|
||||
'assistant.hint.closed.prompt': '{place} jest zamknięte: {day}. Co robimy zamiast tego?',
|
||||
'assistant.hint.opensLate': '{place} otwiera się dopiero o {time} ({day})',
|
||||
'assistant.hint.closesEarly': '{place} zamyka się już o {time} ({day})',
|
||||
'assistant.hint.outsideHours.prompt':
|
||||
'Nasza wizyta w {place} ({day}) nie pasuje do godzin otwarcia. Jak najlepiej ją przesunąć?',
|
||||
'assistant.hint.gap': '{day}: wolne godziny od {from}: {count}',
|
||||
'assistant.hint.gap.one': '{day}: {count} godzina wolna od {from}',
|
||||
'assistant.hint.gap.few': '{day}: {count} godziny wolne od {from}',
|
||||
'assistant.hint.gap.many': '{day}: {count} godzin wolnych od {from}',
|
||||
'assistant.hint.gap.prompt': '{day} od {from} do {to} nic nie jest zaplanowane. Co by tam pasowało?',
|
||||
'assistant.hint.longLeg': '{from} i {to} dzieli w linii prostej {distance}',
|
||||
'assistant.hint.longLeg.prompt': 'Czy {day} z {from} do {to} nie jest za daleko? Jak najlepiej tam dotrzeć?',
|
||||
'assistant.hint.tightLeg': 'Między {from} a {to} zostaje tylko minut: {count}',
|
||||
'assistant.hint.tightLeg.one': 'Między {from} a {to} zostaje tylko {count} minuta',
|
||||
'assistant.hint.tightLeg.few': 'Między {from} a {to} zostają tylko {count} minuty',
|
||||
'assistant.hint.tightLeg.many': 'Między {from} a {to} zostaje tylko {count} minut',
|
||||
'assistant.hint.tightLeg.prompt': 'Czy {day} wystarczy czasu na drogę z {from} do {to}? Co powinniśmy zmienić?',
|
||||
'assistant.hint.noStay': 'Brak noclegu od {day}, nocy: {count}',
|
||||
'assistant.hint.noStay.one': 'Brak noclegu na {count} noc od {day}',
|
||||
'assistant.hint.noStay.few': 'Brak noclegu na {count} noce od {day}',
|
||||
'assistant.hint.noStay.many': 'Brak noclegu na {count} nocy od {day}',
|
||||
'assistant.hint.noStay.prompt': 'Od {day} wciąż brakuje nam noclegu. Co proponujesz?',
|
||||
'assistant.hint.unplacedBooking': '{title} nie jest przypisane do żadnego dnia',
|
||||
'assistant.hint.unplacedBookingDay': '{title} pewnie należy do: {day}',
|
||||
'assistant.hint.unplacedBookings': 'Rezerwacje bez przypisanego dnia: {count}',
|
||||
'assistant.hint.unplacedBookings.one': '{count} rezerwacja nie jest przypisana do żadnego dnia',
|
||||
'assistant.hint.unplacedBookings.few': '{count} rezerwacje nie są przypisane do żadnego dnia',
|
||||
'assistant.hint.unplacedBookings.many': '{count} rezerwacji nie jest przypisanych do żadnego dnia',
|
||||
'assistant.hint.unplacedBooking.prompt': 'Które rezerwacje nie są przypisane do żadnego dnia i gdzie pasują?',
|
||||
'assistant.hint.menuOn': 'Pokazuj wskazówki',
|
||||
'assistant.hint.menuOff': 'Nie pokazuj wskazówek',
|
||||
'assistant.launcher.withHints': 'Asystent, wskazówki: {count}',
|
||||
'assistant.launcher.withHints.one': 'Asystent, {count} wskazówka',
|
||||
'assistant.launcher.withHints.few': 'Asystent, {count} wskazówki',
|
||||
'assistant.launcher.withHints.many': 'Asystent, {count} wskazówek',
|
||||
'assistant.mobile.tileHints': 'Rzeczy do sprawdzenia w planie: {count}',
|
||||
'assistant.mobile.tileHints.one': '{count} rzecz do sprawdzenia w planie',
|
||||
'assistant.mobile.tileHints.few': '{count} rzeczy do sprawdzenia w planie',
|
||||
'assistant.mobile.tileHints.many': '{count} rzeczy do sprawdzenia w planie',
|
||||
'assistant.share.button': 'Udostępnij na czacie podróży',
|
||||
'assistant.share.title': 'Udostępnij na czacie podróży',
|
||||
'assistant.share.who': 'Wszyscy w tej podróży zobaczą tę wiadomość, oznaczoną jako od asystenta.',
|
||||
'assistant.share.includeChanges': 'Dołącz zastosowane zmiany',
|
||||
'assistant.share.changesHeading': 'Zastosowano:',
|
||||
'assistant.share.truncated': 'Skrócono do znaków: {count}',
|
||||
'assistant.share.truncated.one': 'Skrócono do {count} znaku',
|
||||
'assistant.share.truncated.few': 'Skrócono do {count} znaków',
|
||||
'assistant.share.truncated.many': 'Skrócono do {count} znaków',
|
||||
'assistant.share.send': 'Udostępnij',
|
||||
'assistant.share.done': 'Udostępniono na czacie podróży',
|
||||
'assistant.share.shared': 'Udostępniono',
|
||||
'assistant.share.failed': 'Nie udało się udostępnić. Spróbuj ponownie za chwilę.',
|
||||
'assistant.share.chatOff': 'Czat podróży jest wyłączony.',
|
||||
};
|
||||
export default assistant;
|
||||
@@ -116,5 +116,7 @@ const collab: TranslationStrings = {
|
||||
'collab.chat.attachImages': 'Dołącz obrazy',
|
||||
'collab.chat.attachedImage': 'Załączony obraz',
|
||||
'collab.chat.closeImage': 'Zamknij obraz',
|
||||
'collab.chat.fromAssistant': 'od asystenta',
|
||||
'collab.chat.fromAssistantHint': '{name} udostępnia tę odpowiedź asystenta',
|
||||
};
|
||||
export default collab;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import admin from './admin';
|
||||
import airport from './airport';
|
||||
import assistant from './assistant';
|
||||
import atlas from './atlas';
|
||||
import backup from './backup';
|
||||
import budget from './budget';
|
||||
@@ -116,5 +117,6 @@ const locale = {
|
||||
...dawarich,
|
||||
...docsync,
|
||||
...ferratas,
|
||||
...assistant,
|
||||
};
|
||||
export default locale;
|
||||
|
||||
@@ -84,6 +84,7 @@ const oauth: TranslationStrings = {
|
||||
'oauth.authorize.loginButton': 'Sign in to TREK', // en-fallback
|
||||
'oauth.authorize.requestLabel': 'Authorization Request', // en-fallback
|
||||
'oauth.authorize.requestDescription': 'This application is requesting access to your TREK account.', // en-fallback
|
||||
'oauth.authorize.verifiedHost': 'Zweryfikowana domena: {host}',
|
||||
'oauth.authorize.trustNote': 'Only grant access to applications you trust. Your data stays on your server.', // en-fallback
|
||||
'oauth.authorize.selectScope': 'Select at least one scope', // en-fallback
|
||||
'oauth.authorize.approveScopes': 'Zatwierdź ({count} uprawnienia)',
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user