@better-i18n/schemas 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -0
- package/package.json +30 -0
- package/src/ai-models.ts +275 -0
- package/src/github.ts +119 -0
- package/src/index.ts +8 -0
- package/src/languages/index.ts +129 -0
- package/src/organizations/index.ts +4 -0
- package/src/organizations/organization.input.ts +23 -0
- package/src/projects/index.ts +4 -0
- package/src/projects/project.input.ts +116 -0
- package/src/sync.ts +80 -0
- package/src/translations.ts +240 -0
package/README.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# @better-i18n/schemas
|
|
2
|
+
|
|
3
|
+
Shared validation schemas for API and frontend applications.
|
|
4
|
+
|
|
5
|
+
## Structure
|
|
6
|
+
|
|
7
|
+
- `src/projects/` - Project-related validation schemas
|
|
8
|
+
- `src/organizations/` - Organization validation schemas
|
|
9
|
+
- `src/languages/` - Language validation schemas
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { createProjectSchema } from "@better-i18n/schemas/projects";
|
|
15
|
+
import { createOrganizationSchema } from "@better-i18n/schemas/organizations";
|
|
16
|
+
|
|
17
|
+
// Use in tRPC routers
|
|
18
|
+
create: protectedProcedure
|
|
19
|
+
.input(createProjectSchema)
|
|
20
|
+
.mutation(async ({ ctx, input }) => {
|
|
21
|
+
// ...
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
// Use in TanStack Form
|
|
25
|
+
const form = useForm({
|
|
26
|
+
validatorAdapter: zodValidator(),
|
|
27
|
+
validators: {
|
|
28
|
+
onChange: createProjectSchema,
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Benefits
|
|
34
|
+
|
|
35
|
+
- Single source of truth for validation
|
|
36
|
+
- Type safety across API and frontend
|
|
37
|
+
- DRY principle
|
|
38
|
+
- Consistent error messages
|
|
39
|
+
|
|
40
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@better-i18n/schemas",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Shared validation schemas for API and frontend",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/index.ts",
|
|
7
|
+
"types": "./src/index.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./src/index.ts",
|
|
10
|
+
"./projects": "./src/projects/index.ts",
|
|
11
|
+
"./organizations": "./src/organizations/index.ts",
|
|
12
|
+
"./languages": "./src/languages/index.ts",
|
|
13
|
+
"./github": "./src/github.ts",
|
|
14
|
+
"./translations": "./src/translations.ts"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"src"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"type-check": "tsc --noEmit"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"zod": "^3.25.76"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@better-i18n/typescript-config": "workspace:*",
|
|
27
|
+
"typescript": "~5.9.2"
|
|
28
|
+
},
|
|
29
|
+
"license": "MIT"
|
|
30
|
+
}
|
package/src/ai-models.ts
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared AI Model Configuration
|
|
3
|
+
* Single source of truth for all AI model definitions used across frontend and backend.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Provider types
|
|
7
|
+
export type ModelProvider =
|
|
8
|
+
| "better-ai"
|
|
9
|
+
| "openai"
|
|
10
|
+
| "gemini"
|
|
11
|
+
| "claude"
|
|
12
|
+
| "openrouter";
|
|
13
|
+
|
|
14
|
+
// Model categories
|
|
15
|
+
export type ModelCategory =
|
|
16
|
+
| "flagship"
|
|
17
|
+
| "reasoning"
|
|
18
|
+
| "performance"
|
|
19
|
+
| "specialized"
|
|
20
|
+
| "legacy";
|
|
21
|
+
|
|
22
|
+
// Icon types for models
|
|
23
|
+
export type ModelIcon = "OpenAI" | "Gemini" | "Claude" | "BetterAI";
|
|
24
|
+
|
|
25
|
+
// Model color configuration
|
|
26
|
+
export interface ModelColors {
|
|
27
|
+
/** Background color for UI elements */
|
|
28
|
+
background: string;
|
|
29
|
+
/** Hover background color */
|
|
30
|
+
hover: string;
|
|
31
|
+
/** Icon/text color */
|
|
32
|
+
icon: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Complete model configuration
|
|
36
|
+
export interface AIModelConfig {
|
|
37
|
+
/** UI display ID (e.g., "gpt-5.2", "gemini-3-flash") */
|
|
38
|
+
id: string;
|
|
39
|
+
/** Display name for UI (e.g., "ChatGPT 5.2", "Gemini 3 Flash") */
|
|
40
|
+
name: string;
|
|
41
|
+
/** Provider identifier */
|
|
42
|
+
provider: ModelProvider;
|
|
43
|
+
/** Icon to display for this model (from lucide-react or custom) */
|
|
44
|
+
icon: "OpenAI" | "Gemini" | "Claude" | "BetterAI";
|
|
45
|
+
/** Colors for UI elements */
|
|
46
|
+
colors: ModelColors;
|
|
47
|
+
/** Actual API model ID to send to provider SDK (e.g., "gpt-5.2", "gemini-2.5-flash") */
|
|
48
|
+
apiModelId: string;
|
|
49
|
+
/** Max context window in tokens */
|
|
50
|
+
contextSize: number;
|
|
51
|
+
/** Max output tokens */
|
|
52
|
+
maxOutput: number;
|
|
53
|
+
/** Model description */
|
|
54
|
+
description: string;
|
|
55
|
+
/** Optional badge (e.g., "New", "Fast", "Default") */
|
|
56
|
+
badge?: string;
|
|
57
|
+
/** Is this the default model? */
|
|
58
|
+
isDefault?: boolean;
|
|
59
|
+
/** Release date info */
|
|
60
|
+
releaseDate?: string;
|
|
61
|
+
/** Training data cutoff */
|
|
62
|
+
trainingData?: string;
|
|
63
|
+
/** Speed score 0-100 */
|
|
64
|
+
speed?: number;
|
|
65
|
+
/** Model category */
|
|
66
|
+
category?: ModelCategory;
|
|
67
|
+
/** Whether this model requires user's own API key (not platform key) */
|
|
68
|
+
requiresUserKey?: boolean;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Provider configuration (for UI)
|
|
72
|
+
export interface ProviderInfo {
|
|
73
|
+
id: ModelProvider;
|
|
74
|
+
name: string;
|
|
75
|
+
brandColor: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* All available AI models - Single Source of Truth
|
|
80
|
+
*
|
|
81
|
+
* Important: `apiModelId` is the actual model ID sent to the provider's SDK.
|
|
82
|
+
* This may differ from `id` which is used for UI/selection purposes.
|
|
83
|
+
*
|
|
84
|
+
* We keep only the best/flagship models from each provider to avoid clutter.
|
|
85
|
+
*/
|
|
86
|
+
export const AI_MODELS: AIModelConfig[] = [
|
|
87
|
+
// ============================================
|
|
88
|
+
// Better AI - Platform Default (Powered by Gemini 3 Flash)
|
|
89
|
+
// ============================================
|
|
90
|
+
{
|
|
91
|
+
id: "better-ai",
|
|
92
|
+
name: "Better AI",
|
|
93
|
+
provider: "better-ai",
|
|
94
|
+
icon: "BetterAI",
|
|
95
|
+
colors: {
|
|
96
|
+
background: "bg-gray-50 dark:bg-neutral-700",
|
|
97
|
+
hover: "hover:bg-gray-100 dark:hover:bg-neutral-600",
|
|
98
|
+
icon: "text-gray-700 dark:text-white",
|
|
99
|
+
},
|
|
100
|
+
apiModelId: "gemini-3-flash-preview",
|
|
101
|
+
contextSize: 1_048_576, // 1M tokens (verified from Google docs)
|
|
102
|
+
maxOutput: 65_536,
|
|
103
|
+
description:
|
|
104
|
+
"Fast and intelligent AI model powered by Gemini 3 Flash. No API key required.",
|
|
105
|
+
isDefault: true,
|
|
106
|
+
badge: "Default",
|
|
107
|
+
releaseDate: "Jan 2025",
|
|
108
|
+
trainingData: "Jan 2025",
|
|
109
|
+
speed: 95,
|
|
110
|
+
category: "flagship",
|
|
111
|
+
requiresUserKey: false,
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
// ============================================
|
|
115
|
+
// OpenAI Models (via OpenRouter)
|
|
116
|
+
// ============================================
|
|
117
|
+
{
|
|
118
|
+
id: "gpt-5.2",
|
|
119
|
+
name: "GPT 5.2",
|
|
120
|
+
provider: "openrouter",
|
|
121
|
+
icon: "OpenAI",
|
|
122
|
+
colors: {
|
|
123
|
+
background: "bg-gray-200 dark:bg-neutral-700",
|
|
124
|
+
hover: "hover:bg-gray-300 dark:hover:bg-neutral-600",
|
|
125
|
+
icon: "text-gray-600 dark:text-white",
|
|
126
|
+
},
|
|
127
|
+
apiModelId: "openai/gpt-5.2",
|
|
128
|
+
contextSize: 400_000, // 400K tokens (GPT-5 series standard)
|
|
129
|
+
maxOutput: 128_000,
|
|
130
|
+
description: "OpenAI's latest flagship model via OpenRouter.",
|
|
131
|
+
badge: "New",
|
|
132
|
+
releaseDate: "2025",
|
|
133
|
+
trainingData: "2025",
|
|
134
|
+
speed: 95,
|
|
135
|
+
category: "flagship",
|
|
136
|
+
requiresUserKey: false,
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
id: "gpt-5.1",
|
|
140
|
+
name: "GPT 5.1",
|
|
141
|
+
provider: "openrouter",
|
|
142
|
+
icon: "OpenAI",
|
|
143
|
+
colors: {
|
|
144
|
+
background: "bg-gray-200 dark:bg-neutral-700",
|
|
145
|
+
hover: "hover:bg-gray-300 dark:hover:bg-neutral-600",
|
|
146
|
+
icon: "text-gray-600 dark:text-white",
|
|
147
|
+
},
|
|
148
|
+
apiModelId: "openai/gpt-5.1",
|
|
149
|
+
contextSize: 400_000, // 400K tokens (GPT-5 series standard)
|
|
150
|
+
maxOutput: 128_000,
|
|
151
|
+
description: "OpenAI's capable model via OpenRouter.",
|
|
152
|
+
badge: "Fast",
|
|
153
|
+
releaseDate: "2025",
|
|
154
|
+
trainingData: "2025",
|
|
155
|
+
speed: 93,
|
|
156
|
+
category: "flagship",
|
|
157
|
+
requiresUserKey: false,
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
// ============================================
|
|
161
|
+
// Google Gemini Models
|
|
162
|
+
// ============================================
|
|
163
|
+
{
|
|
164
|
+
id: "gemini-3-pro",
|
|
165
|
+
name: "Gemini 3 Pro",
|
|
166
|
+
provider: "gemini",
|
|
167
|
+
icon: "Gemini",
|
|
168
|
+
colors: {
|
|
169
|
+
background: "bg-blue-50 dark:bg-blue-700",
|
|
170
|
+
hover: "hover:bg-blue-100 dark:hover:bg-blue-600",
|
|
171
|
+
icon: "text-blue-400 dark:text-blue-400",
|
|
172
|
+
},
|
|
173
|
+
apiModelId: "gemini-3-pro-preview",
|
|
174
|
+
contextSize: 1_048_576, // 1M tokens (verified from Google docs)
|
|
175
|
+
maxOutput: 65_536,
|
|
176
|
+
description: "Google's most capable model. Best for complex tasks.",
|
|
177
|
+
badge: "Pro",
|
|
178
|
+
releaseDate: "Jan 2025",
|
|
179
|
+
trainingData: "Jan 2025",
|
|
180
|
+
speed: 90,
|
|
181
|
+
category: "flagship",
|
|
182
|
+
requiresUserKey: false,
|
|
183
|
+
},
|
|
184
|
+
|
|
185
|
+
// ============================================
|
|
186
|
+
// Anthropic Claude (via OpenRouter) - Moved to bottom
|
|
187
|
+
// ============================================
|
|
188
|
+
{
|
|
189
|
+
id: "claude-4-sonnet",
|
|
190
|
+
name: "Claude Sonnet 4",
|
|
191
|
+
provider: "openrouter",
|
|
192
|
+
icon: "Claude",
|
|
193
|
+
colors: {
|
|
194
|
+
background: "bg-orange-50 dark:bg-orange-700",
|
|
195
|
+
hover: "hover:bg-orange-100 dark:hover:bg-orange-600",
|
|
196
|
+
icon: "text-orange-400 dark:text-orange-400",
|
|
197
|
+
},
|
|
198
|
+
apiModelId: "anthropic/claude-sonnet-4.5",
|
|
199
|
+
contextSize: 200_000,
|
|
200
|
+
maxOutput: 8_192,
|
|
201
|
+
description:
|
|
202
|
+
"Anthropic's flagship. Excellent reasoning and writing via OpenRouter.",
|
|
203
|
+
badge: "New",
|
|
204
|
+
releaseDate: "2025",
|
|
205
|
+
speed: 90,
|
|
206
|
+
category: "flagship",
|
|
207
|
+
requiresUserKey: false,
|
|
208
|
+
},
|
|
209
|
+
];
|
|
210
|
+
|
|
211
|
+
// ============================================
|
|
212
|
+
// Helper Functions
|
|
213
|
+
// ============================================
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Get model config by ID
|
|
217
|
+
*/
|
|
218
|
+
export function getModelById(modelId: string): AIModelConfig | undefined {
|
|
219
|
+
return AI_MODELS.find((m) => m.id === modelId);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Get the actual API model ID for a given UI model ID
|
|
224
|
+
*/
|
|
225
|
+
export function getApiModelId(modelId: string): string {
|
|
226
|
+
const model = getModelById(modelId);
|
|
227
|
+
return model?.apiModelId ?? modelId;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Get provider for a model ID
|
|
232
|
+
*/
|
|
233
|
+
export function getModelProvider(modelId: string): ModelProvider {
|
|
234
|
+
const model = getModelById(modelId);
|
|
235
|
+
return model?.provider ?? "better-ai";
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Get default model
|
|
240
|
+
*/
|
|
241
|
+
export function getDefaultModel(): AIModelConfig {
|
|
242
|
+
return AI_MODELS.find((m) => m.isDefault) ?? AI_MODELS[0];
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Get models by provider
|
|
247
|
+
*/
|
|
248
|
+
export function getModelsByProvider(provider: ModelProvider): AIModelConfig[] {
|
|
249
|
+
return AI_MODELS.filter((m) => m.provider === provider);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Get models that don't require user API key (platform-provided)
|
|
254
|
+
*/
|
|
255
|
+
export function getPlatformModels(): AIModelConfig[] {
|
|
256
|
+
return AI_MODELS.filter((m) => !m.requiresUserKey);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Check if a model requires user's own API key
|
|
261
|
+
*/
|
|
262
|
+
export function modelRequiresUserKey(modelId: string): boolean {
|
|
263
|
+
const model = getModelById(modelId);
|
|
264
|
+
return model?.requiresUserKey ?? false;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Format context size for display (e.g., "128K", "2M")
|
|
269
|
+
*/
|
|
270
|
+
export function formatContextSize(tokens: number): string {
|
|
271
|
+
if (tokens >= 1_000_000) {
|
|
272
|
+
return `${(tokens / 1_000_000).toFixed(0)}M`;
|
|
273
|
+
}
|
|
274
|
+
return `${(tokens / 1_000).toFixed(0)}K`;
|
|
275
|
+
}
|
package/src/github.ts
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Schema for listing GitHub repositories from an installation.
|
|
6
|
+
*/
|
|
7
|
+
export const listRepositoriesSchema = z.object({
|
|
8
|
+
installationId: z.number(),
|
|
9
|
+
page: z.number().min(1).default(1),
|
|
10
|
+
limit: z.number().min(1).max(100).default(30),
|
|
11
|
+
type: z.enum(["all", "public", "private"]).default("all"),
|
|
12
|
+
sort: z
|
|
13
|
+
.enum(["created", "updated", "pushed", "full_name"])
|
|
14
|
+
.default("updated"),
|
|
15
|
+
direction: z.enum(["asc", "desc"]).default("desc"),
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Schema for getting a specific GitHub repository.
|
|
20
|
+
*/
|
|
21
|
+
export const getRepositorySchema = z.object({
|
|
22
|
+
installationId: z.number(),
|
|
23
|
+
owner: z.string().min(1),
|
|
24
|
+
repo: z.string().min(1),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Schema for adding a GitHub repository to a project.
|
|
29
|
+
*/
|
|
30
|
+
export const addRepositorySchema = z.object({
|
|
31
|
+
projectId: z.string().uuid(),
|
|
32
|
+
installationId: z.number(),
|
|
33
|
+
owner: z.string().min(1),
|
|
34
|
+
repo: z.string().min(1),
|
|
35
|
+
path: z.string().default(""),
|
|
36
|
+
pattern: z.string().default(""),
|
|
37
|
+
format: z.enum(["JSON_FLAT", "JSON_NAMESPACED"]).default("JSON_FLAT"),
|
|
38
|
+
branch: z.string().optional(),
|
|
39
|
+
targetBranch: z.string().default("i18n-sync"),
|
|
40
|
+
autoPushToSourceBranch: z.boolean().default(false),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Schema for updating a GitHub repository.
|
|
45
|
+
*/
|
|
46
|
+
export const updateRepositorySchema = z.object({
|
|
47
|
+
repositoryId: z.string().uuid(),
|
|
48
|
+
path: z.string().optional(),
|
|
49
|
+
pattern: z.string().optional(),
|
|
50
|
+
format: z.enum(["JSON_FLAT", "JSON_NAMESPACED"]).optional(),
|
|
51
|
+
branch: z.string().optional(),
|
|
52
|
+
targetBranch: z.string().optional(),
|
|
53
|
+
enabled: z.boolean().optional(),
|
|
54
|
+
autoPushToSourceBranch: z.boolean().optional(),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Schema for removing a GitHub repository from a project.
|
|
59
|
+
*/
|
|
60
|
+
export const removeRepositorySchema = z.object({
|
|
61
|
+
repositoryId: z.string().uuid(),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Schema for syncing a GitHub repository.
|
|
66
|
+
*/
|
|
67
|
+
export const syncRepositorySchema = z.object({
|
|
68
|
+
repositoryId: z.string().uuid(),
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Schema for GitHub OAuth callback.
|
|
73
|
+
*/
|
|
74
|
+
export const oauthCallbackSchema = z.object({
|
|
75
|
+
code: z.string().min(1),
|
|
76
|
+
state: z.string().optional(),
|
|
77
|
+
installation_id: z.string().optional(), // GitHub App installation ID (as string)
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Schema for listing GitHub repository branches.
|
|
82
|
+
*/
|
|
83
|
+
export const listBranchesSchema = z.object({
|
|
84
|
+
installationId: z.number(),
|
|
85
|
+
owner: z.string().min(1),
|
|
86
|
+
repo: z.string().min(1),
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Schema for getting GitHub repository tree.
|
|
91
|
+
*/
|
|
92
|
+
export const getTreeSchema = z.object({
|
|
93
|
+
installationId: z.number(),
|
|
94
|
+
owner: z.string().min(1),
|
|
95
|
+
repo: z.string().min(1),
|
|
96
|
+
branch: z.string().min(1),
|
|
97
|
+
path: z.string().default(""),
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Schema for getting source files from a repository.
|
|
102
|
+
*/
|
|
103
|
+
export const getSourceFilesSchema = z.object({
|
|
104
|
+
repositoryId: z.string().uuid(),
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Types for GitHub repository operations.
|
|
109
|
+
*/
|
|
110
|
+
export type ListRepositoriesInput = z.infer<typeof listRepositoriesSchema>;
|
|
111
|
+
export type GetRepositoryInput = z.infer<typeof getRepositorySchema>;
|
|
112
|
+
export type AddRepositoryInput = z.infer<typeof addRepositorySchema>;
|
|
113
|
+
export type UpdateRepositoryInput = z.infer<typeof updateRepositorySchema>;
|
|
114
|
+
export type RemoveRepositoryInput = z.infer<typeof removeRepositorySchema>;
|
|
115
|
+
export type SyncRepositoryInput = z.infer<typeof syncRepositorySchema>;
|
|
116
|
+
export type OAuthCallbackInput = z.infer<typeof oauthCallbackSchema>;
|
|
117
|
+
export type ListBranchesInput = z.infer<typeof listBranchesSchema>;
|
|
118
|
+
export type GetTreeInput = z.infer<typeof getTreeSchema>;
|
|
119
|
+
export type GetSourceFilesInput = z.infer<typeof getSourceFilesSchema>;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Language utilities and mappings
|
|
3
|
+
*
|
|
4
|
+
* Shared across the monorepo for consistent language handling.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Map common language codes to display names
|
|
8
|
+
export const LANGUAGE_NAMES: Record<string, string> = {
|
|
9
|
+
en: "English",
|
|
10
|
+
tr: "Turkish",
|
|
11
|
+
de: "German",
|
|
12
|
+
fr: "French",
|
|
13
|
+
es: "Spanish",
|
|
14
|
+
it: "Italian",
|
|
15
|
+
pt: "Portuguese",
|
|
16
|
+
ru: "Russian",
|
|
17
|
+
zh: "Chinese",
|
|
18
|
+
ja: "Japanese",
|
|
19
|
+
ko: "Korean",
|
|
20
|
+
ar: "Arabic",
|
|
21
|
+
pl: "Polish",
|
|
22
|
+
nl: "Dutch",
|
|
23
|
+
sv: "Swedish",
|
|
24
|
+
no: "Norwegian",
|
|
25
|
+
da: "Danish",
|
|
26
|
+
fi: "Finnish",
|
|
27
|
+
cs: "Czech",
|
|
28
|
+
hu: "Hungarian",
|
|
29
|
+
ro: "Romanian",
|
|
30
|
+
uk: "Ukrainian",
|
|
31
|
+
vi: "Vietnamese",
|
|
32
|
+
th: "Thai",
|
|
33
|
+
id: "Indonesian",
|
|
34
|
+
ms: "Malay",
|
|
35
|
+
hi: "Hindi",
|
|
36
|
+
bn: "Bengali",
|
|
37
|
+
he: "Hebrew",
|
|
38
|
+
el: "Greek",
|
|
39
|
+
bg: "Bulgarian",
|
|
40
|
+
sk: "Slovak",
|
|
41
|
+
sl: "Slovenian",
|
|
42
|
+
hr: "Croatian",
|
|
43
|
+
sr: "Serbian",
|
|
44
|
+
lt: "Lithuanian",
|
|
45
|
+
lv: "Latvian",
|
|
46
|
+
et: "Estonian",
|
|
47
|
+
ca: "Catalan",
|
|
48
|
+
eu: "Basque",
|
|
49
|
+
gl: "Galician",
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// Map common language codes to country codes for flags
|
|
53
|
+
export const LANGUAGE_TO_COUNTRY: Record<string, string> = {
|
|
54
|
+
en: "gb",
|
|
55
|
+
tr: "tr",
|
|
56
|
+
de: "de",
|
|
57
|
+
fr: "fr",
|
|
58
|
+
es: "es",
|
|
59
|
+
it: "it",
|
|
60
|
+
pt: "pt",
|
|
61
|
+
ru: "ru",
|
|
62
|
+
zh: "cn",
|
|
63
|
+
ja: "jp",
|
|
64
|
+
ko: "kr",
|
|
65
|
+
ar: "sa",
|
|
66
|
+
pl: "pl",
|
|
67
|
+
nl: "nl",
|
|
68
|
+
sv: "se",
|
|
69
|
+
no: "no",
|
|
70
|
+
da: "dk",
|
|
71
|
+
fi: "fi",
|
|
72
|
+
cs: "cz",
|
|
73
|
+
hu: "hu",
|
|
74
|
+
ro: "ro",
|
|
75
|
+
uk: "ua",
|
|
76
|
+
vi: "vn",
|
|
77
|
+
th: "th",
|
|
78
|
+
id: "id",
|
|
79
|
+
ms: "my",
|
|
80
|
+
hi: "in",
|
|
81
|
+
bn: "bd",
|
|
82
|
+
he: "il",
|
|
83
|
+
el: "gr",
|
|
84
|
+
bg: "bg",
|
|
85
|
+
sk: "sk",
|
|
86
|
+
sl: "si",
|
|
87
|
+
hr: "hr",
|
|
88
|
+
sr: "rs",
|
|
89
|
+
lt: "lt",
|
|
90
|
+
lv: "lv",
|
|
91
|
+
et: "ee",
|
|
92
|
+
ca: "es-ct",
|
|
93
|
+
eu: "es-pv",
|
|
94
|
+
gl: "es-ga",
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get country code from language code for flag mapping
|
|
99
|
+
*/
|
|
100
|
+
export function getCountryCode(langCode: string): string {
|
|
101
|
+
const code = langCode.toLowerCase();
|
|
102
|
+
// Handle locale variants (e.g. en-US -> us)
|
|
103
|
+
if (code.includes("-")) {
|
|
104
|
+
const parts = code.split("-");
|
|
105
|
+
return parts[1].toLowerCase();
|
|
106
|
+
}
|
|
107
|
+
return LANGUAGE_TO_COUNTRY[code] || code;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Get language name from code, with fallback to uppercase code
|
|
112
|
+
*/
|
|
113
|
+
export function getLanguageName(code: string): string {
|
|
114
|
+
return LANGUAGE_NAMES[code.toLowerCase()] || code.toUpperCase();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Get language code from name (reverse lookup)
|
|
119
|
+
* Returns undefined if not found
|
|
120
|
+
*/
|
|
121
|
+
export function getLanguageCode(name: string): string | undefined {
|
|
122
|
+
const lowerName = name.toLowerCase();
|
|
123
|
+
for (const [code, langName] of Object.entries(LANGUAGE_NAMES)) {
|
|
124
|
+
if (langName.toLowerCase() === lowerName) {
|
|
125
|
+
return code;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Schema for creating a new organization.
|
|
6
|
+
*/
|
|
7
|
+
export const createOrganizationSchema = z.object({
|
|
8
|
+
name: z
|
|
9
|
+
.string()
|
|
10
|
+
.min(1, "Organization name is required")
|
|
11
|
+
.max(100, "Organization name must be less than 100 characters"),
|
|
12
|
+
slug: z
|
|
13
|
+
.string()
|
|
14
|
+
.regex(/^[a-z0-9-]+$/, "Slug can only contain lowercase letters, numbers, and hyphens")
|
|
15
|
+
.min(1, "Slug is required")
|
|
16
|
+
.max(50, "Slug must be less than 50 characters"),
|
|
17
|
+
logo: z.string().url("Invalid logo URL").optional(),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// Type exports
|
|
21
|
+
export type CreateOrganizationInput = z.infer<typeof createOrganizationSchema>;
|
|
22
|
+
|
|
23
|
+
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Schema for creating a new project.
|
|
6
|
+
*/
|
|
7
|
+
export const createProjectSchema = z.object({
|
|
8
|
+
name: z.string().min(1, "Project name is required").max(100, "Project name must be less than 100 characters"),
|
|
9
|
+
slug: z
|
|
10
|
+
.string()
|
|
11
|
+
.regex(/^[a-z0-9-]+$/, "Slug can only contain lowercase letters, numbers, and hyphens")
|
|
12
|
+
.min(1, "Slug is required")
|
|
13
|
+
.max(50, "Slug must be less than 50 characters"),
|
|
14
|
+
sourceLanguage: z.string().length(2, "Invalid language code"),
|
|
15
|
+
organizationId: z.string().uuid("Invalid organization ID"),
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Schema for updating a project.
|
|
20
|
+
*/
|
|
21
|
+
export const updateProjectSchema = z.object({
|
|
22
|
+
projectId: z.string().uuid("Invalid project ID"),
|
|
23
|
+
name: z.string().min(1).max(100).optional(),
|
|
24
|
+
slug: z.string().regex(/^[a-z0-9-]+$/).optional(),
|
|
25
|
+
sourceLanguage: z.string().length(2, "Invalid language code").optional(),
|
|
26
|
+
autoTranslate: z.boolean().optional(),
|
|
27
|
+
aiSystemPrompt: z.string().max(2000, "System prompt must be less than 2000 characters").optional().nullable(),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Schema for project list query.
|
|
32
|
+
*/
|
|
33
|
+
export const listProjectsSchema = z.object({
|
|
34
|
+
organizationId: z.string().uuid("Invalid organization ID"),
|
|
35
|
+
page: z.number().int().positive().default(1),
|
|
36
|
+
limit: z.number().int().positive().max(100).default(20),
|
|
37
|
+
search: z.string().optional(),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Schema for getting a project by ID.
|
|
42
|
+
*/
|
|
43
|
+
export const getProjectSchema = z.object({
|
|
44
|
+
projectId: z.string().uuid("Invalid project ID"),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Schema for getting a project by slug.
|
|
49
|
+
*/
|
|
50
|
+
export const getProjectBySlugSchema = z.object({
|
|
51
|
+
organizationId: z.string().uuid("Invalid organization ID"),
|
|
52
|
+
slug: z.string().regex(/^[a-z0-9-]+$/, "Invalid project slug"),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Schema for deleting a project.
|
|
57
|
+
*/
|
|
58
|
+
export const deleteProjectSchema = z.object({
|
|
59
|
+
projectId: z.string().uuid("Invalid project ID"),
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Schema for adding a target language to a project.
|
|
64
|
+
*/
|
|
65
|
+
export const addTargetLanguageSchema = z.object({
|
|
66
|
+
projectId: z.string().uuid("Invalid project ID"),
|
|
67
|
+
languageCode: z.string().length(2, "Invalid language code"),
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Schema for removing a target language from a project.
|
|
72
|
+
*/
|
|
73
|
+
export const removeTargetLanguageSchema = z.object({
|
|
74
|
+
projectId: z.string().uuid("Invalid project ID"),
|
|
75
|
+
languageCode: z.string().length(2, "Invalid language code"),
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Schema for toggling a target language's disabled state.
|
|
80
|
+
*/
|
|
81
|
+
export const toggleTargetLanguageSchema = z.object({
|
|
82
|
+
projectId: z.string().uuid("Invalid project ID"),
|
|
83
|
+
languageCode: z.string().length(2, "Invalid language code"),
|
|
84
|
+
disabled: z.boolean(),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Schema for checking project slug availability within an organization.
|
|
89
|
+
*/
|
|
90
|
+
export const checkProjectSlugAvailabilitySchema = z.object({
|
|
91
|
+
organizationId: z.string().uuid("Invalid organization ID"),
|
|
92
|
+
slug: z.string().min(1).max(50).regex(/^[a-z0-9-]+$/, {
|
|
93
|
+
message: "Slug can only contain lowercase letters, numbers, and hyphens",
|
|
94
|
+
}),
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Schema for getting project target languages.
|
|
99
|
+
*/
|
|
100
|
+
export const getTargetLanguagesSchema = z.object({
|
|
101
|
+
projectId: z.string().uuid("Invalid project ID"),
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Type exports
|
|
105
|
+
export type CreateProjectInput = z.infer<typeof createProjectSchema>;
|
|
106
|
+
export type UpdateProjectInput = z.infer<typeof updateProjectSchema>;
|
|
107
|
+
export type ListProjectsInput = z.infer<typeof listProjectsSchema>;
|
|
108
|
+
export type GetProjectInput = z.infer<typeof getProjectSchema>;
|
|
109
|
+
export type GetProjectBySlugInput = z.infer<typeof getProjectBySlugSchema>;
|
|
110
|
+
export type DeleteProjectInput = z.infer<typeof deleteProjectSchema>;
|
|
111
|
+
export type AddTargetLanguageInput = z.infer<typeof addTargetLanguageSchema>;
|
|
112
|
+
export type RemoveTargetLanguageInput = z.infer<typeof removeTargetLanguageSchema>;
|
|
113
|
+
export type ToggleTargetLanguageInput = z.infer<typeof toggleTargetLanguageSchema>;
|
|
114
|
+
export type GetTargetLanguagesInput = z.infer<typeof getTargetLanguagesSchema>;
|
|
115
|
+
|
|
116
|
+
|
package/src/sync.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Schema for triggering a sync operation.
|
|
5
|
+
*/
|
|
6
|
+
export const triggerSyncSchema = z.object({
|
|
7
|
+
repositoryId: z.string().uuid("Invalid repository ID"),
|
|
8
|
+
projectId: z.string().uuid("Invalid project ID"),
|
|
9
|
+
/**
|
|
10
|
+
* Optional: Only sync specific translation keys (incremental sync).
|
|
11
|
+
* When provided, only these keys will be translated instead of full repository sync.
|
|
12
|
+
* This is much faster and more quota-friendly for single key additions.
|
|
13
|
+
*/
|
|
14
|
+
newKeyIds: z.array(z.string().uuid()).optional(),
|
|
15
|
+
/**
|
|
16
|
+
* Optional: Type of sync job to create.
|
|
17
|
+
* - source_sync: Sync source keys from GitHub to DB (default)
|
|
18
|
+
* - publish: Generate translation files from DB and push to GitHub (create PR)
|
|
19
|
+
* - initial_import: Fast initial import of source keys only
|
|
20
|
+
* - bulk_translate: Translate existing keys without importing
|
|
21
|
+
*/
|
|
22
|
+
jobType: z.enum(["source_sync", "publish", "initial_import", "bulk_translate"]).optional().default("source_sync"),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Schema for getting sync status.
|
|
27
|
+
*/
|
|
28
|
+
export const getSyncStatusSchema = z.object({
|
|
29
|
+
jobId: z.string().uuid("Invalid job ID"),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Schema for listing sync history.
|
|
34
|
+
*/
|
|
35
|
+
export const listSyncHistorySchema = z.object({
|
|
36
|
+
repositoryId: z.string().uuid("Invalid repository ID").optional(),
|
|
37
|
+
projectId: z.string().uuid("Invalid project ID").optional(),
|
|
38
|
+
page: z.number().int().positive().default(1),
|
|
39
|
+
limit: z.number().int().positive().max(100).default(20),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Schema for triggering bulk translation.
|
|
44
|
+
*/
|
|
45
|
+
export const triggerBulkTranslationSchema = z.object({
|
|
46
|
+
repositoryId: z.string().uuid("Invalid repository ID"),
|
|
47
|
+
projectId: z.string().uuid("Invalid project ID"),
|
|
48
|
+
/**
|
|
49
|
+
* Optional: Specific key IDs to translate.
|
|
50
|
+
* If not provided, all keys from the repository will be translated.
|
|
51
|
+
*/
|
|
52
|
+
keyIds: z.array(z.string().uuid()).optional(),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Type exports
|
|
56
|
+
export type TriggerSyncInput = z.infer<typeof triggerSyncSchema>;
|
|
57
|
+
export type GetSyncStatusInput = z.infer<typeof getSyncStatusSchema>;
|
|
58
|
+
export type ListSyncHistoryInput = z.infer<typeof listSyncHistorySchema>;
|
|
59
|
+
export type TriggerBulkTranslationInput = z.infer<
|
|
60
|
+
typeof triggerBulkTranslationSchema
|
|
61
|
+
>;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Schema for resyncing an existing sync job.
|
|
65
|
+
*/
|
|
66
|
+
export const resyncJobSchema = z.object({
|
|
67
|
+
jobId: z.string().uuid("Invalid job ID"),
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
export type ResyncJobInput = z.infer<typeof resyncJobSchema>;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Schema for cancelling a sync job.
|
|
74
|
+
*/
|
|
75
|
+
export const cancelJobSchema = z.object({
|
|
76
|
+
jobId: z.string().uuid("Invalid job ID"),
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
export type CancelJobInput = z.infer<typeof cancelJobSchema>;
|
|
80
|
+
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Schema for listing translation keys.
|
|
5
|
+
*/
|
|
6
|
+
export const listTranslationKeysSchema = z.object({
|
|
7
|
+
projectId: z.string().uuid(),
|
|
8
|
+
page: z.number().min(1).default(1),
|
|
9
|
+
limit: z.number().min(1).max(5000).default(30),
|
|
10
|
+
search: z.string().optional(),
|
|
11
|
+
languageCodes: z.array(z.string()).optional(),
|
|
12
|
+
namespaces: z.array(z.string()).optional(),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Schema for getting a specific translation key.
|
|
17
|
+
*/
|
|
18
|
+
export const getTranslationKeySchema = z.object({
|
|
19
|
+
keyId: z.string().uuid(),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Schema for creating a translation key.
|
|
24
|
+
*/
|
|
25
|
+
export const createTranslationKeySchema = z.object({
|
|
26
|
+
projectId: z.string().uuid(),
|
|
27
|
+
key: z.string().min(1),
|
|
28
|
+
sourceValue: z.string().optional(),
|
|
29
|
+
description: z.string().optional(),
|
|
30
|
+
context: z.string().optional(),
|
|
31
|
+
maxLength: z.number().positive().optional(),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Schema for updating a translation key.
|
|
36
|
+
*/
|
|
37
|
+
export const updateTranslationKeySchema = z.object({
|
|
38
|
+
keyId: z.string().uuid(),
|
|
39
|
+
key: z.string().min(1).optional(),
|
|
40
|
+
description: z.string().optional(),
|
|
41
|
+
context: z.string().optional(),
|
|
42
|
+
maxLength: z.number().positive().optional(),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Schema for deleting a translation key.
|
|
47
|
+
*/
|
|
48
|
+
export const deleteTranslationKeySchema = z.object({
|
|
49
|
+
keyId: z.string().uuid(),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Schema for updating a translation.
|
|
54
|
+
* Supports both target language translations and source language edits.
|
|
55
|
+
*/
|
|
56
|
+
export const updateTranslationSchema = z.object({
|
|
57
|
+
keyId: z.string().uuid(),
|
|
58
|
+
languageCode: z.string().min(1),
|
|
59
|
+
value: z.string(),
|
|
60
|
+
status: z
|
|
61
|
+
.enum([
|
|
62
|
+
"draft",
|
|
63
|
+
"pending",
|
|
64
|
+
"needs_review",
|
|
65
|
+
"reviewed",
|
|
66
|
+
"approved",
|
|
67
|
+
"ai_generated",
|
|
68
|
+
])
|
|
69
|
+
.default("approved"), // ✅ Updated status model
|
|
70
|
+
// Source language editing fields
|
|
71
|
+
isSourceLanguage: z.boolean().optional().default(false),
|
|
72
|
+
reason: z.string().optional(), // For audit trail: why source was edited
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Schema for bulk updating translations.
|
|
77
|
+
* Allows updating multiple translations in a single request.
|
|
78
|
+
*/
|
|
79
|
+
export const bulkUpdateTranslationsSchema = z.object({
|
|
80
|
+
projectId: z.string().uuid(),
|
|
81
|
+
translations: z
|
|
82
|
+
.array(
|
|
83
|
+
z.object({
|
|
84
|
+
keyId: z.string().uuid(),
|
|
85
|
+
languageCode: z.string().min(1),
|
|
86
|
+
value: z.string(),
|
|
87
|
+
status: z
|
|
88
|
+
.enum([
|
|
89
|
+
"draft",
|
|
90
|
+
"pending",
|
|
91
|
+
"needs_review",
|
|
92
|
+
"reviewed",
|
|
93
|
+
"approved",
|
|
94
|
+
"ai_generated",
|
|
95
|
+
])
|
|
96
|
+
.default("approved"),
|
|
97
|
+
// Source language editing: when true, updates translationKey.sourceText instead of translation table
|
|
98
|
+
isSourceLanguage: z.boolean().optional().default(false),
|
|
99
|
+
}),
|
|
100
|
+
)
|
|
101
|
+
.min(1),
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Types for translation operations.
|
|
106
|
+
*/
|
|
107
|
+
export type ListTranslationKeysInput = z.infer<
|
|
108
|
+
typeof listTranslationKeysSchema
|
|
109
|
+
>;
|
|
110
|
+
export type GetTranslationKeyInput = z.infer<typeof getTranslationKeySchema>;
|
|
111
|
+
export type CreateTranslationKeyInput = z.infer<
|
|
112
|
+
typeof createTranslationKeySchema
|
|
113
|
+
>;
|
|
114
|
+
export type UpdateTranslationKeyInput = z.infer<
|
|
115
|
+
typeof updateTranslationKeySchema
|
|
116
|
+
>;
|
|
117
|
+
export type DeleteTranslationKeyInput = z.infer<
|
|
118
|
+
typeof deleteTranslationKeySchema
|
|
119
|
+
>;
|
|
120
|
+
export type UpdateTranslationInput = z.infer<typeof updateTranslationSchema>;
|
|
121
|
+
export type BulkUpdateTranslationsInput = z.infer<
|
|
122
|
+
typeof bulkUpdateTranslationsSchema
|
|
123
|
+
>;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Schema for generating AI translation suggestion.
|
|
127
|
+
*/
|
|
128
|
+
export const generateSuggestionSchema = z.object({
|
|
129
|
+
keyId: z.string().uuid(),
|
|
130
|
+
languageCode: z.string().min(1),
|
|
131
|
+
model: z
|
|
132
|
+
.enum([
|
|
133
|
+
"gemini-2.5-pro",
|
|
134
|
+
"gemini-2.5-flash",
|
|
135
|
+
"gemini-2.5-flash-lite",
|
|
136
|
+
"gemini-2.5-flash-lite-preview-06-17",
|
|
137
|
+
"gemini-2.0-flash",
|
|
138
|
+
])
|
|
139
|
+
.default("gemini-2.5-flash"),
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Schema for AI suggestion (simplified, uses project system prompt).
|
|
144
|
+
*/
|
|
145
|
+
export const aiSuggestSchema = z.object({
|
|
146
|
+
keyId: z.string().uuid(),
|
|
147
|
+
sourceText: z.string(),
|
|
148
|
+
targetLanguage: z.string().min(1),
|
|
149
|
+
projectId: z.string().uuid(),
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Schema for publishing translations (changing status from draft to approved).
|
|
154
|
+
*/
|
|
155
|
+
export const publishTranslationsSchema = z.object({
|
|
156
|
+
projectId: z.string().uuid(),
|
|
157
|
+
translations: z
|
|
158
|
+
.array(
|
|
159
|
+
z.object({
|
|
160
|
+
keyId: z.string().uuid(),
|
|
161
|
+
languageCode: z.string().min(1),
|
|
162
|
+
}),
|
|
163
|
+
)
|
|
164
|
+
.min(1),
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Schema for batch sync of translations.
|
|
169
|
+
*/
|
|
170
|
+
export const batchSyncTranslationsSchema = z.object({
|
|
171
|
+
projectId: z.string().uuid(),
|
|
172
|
+
translations: z
|
|
173
|
+
.array(
|
|
174
|
+
z.object({
|
|
175
|
+
keyId: z.string().uuid(),
|
|
176
|
+
languageCode: z.string().min(1),
|
|
177
|
+
value: z.string(),
|
|
178
|
+
baselineValue: z.string(), // Original value when loaded
|
|
179
|
+
status: z
|
|
180
|
+
.enum(["pending", "reviewed", "approved", "conflict", "synced"])
|
|
181
|
+
.default("pending"),
|
|
182
|
+
lastModified: z.string().datetime(), // ISO string
|
|
183
|
+
}),
|
|
184
|
+
)
|
|
185
|
+
.min(1),
|
|
186
|
+
createBaseline: z.boolean().default(true), // Whether to create new baseline after sync
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Schema for conflict detection.
|
|
191
|
+
*/
|
|
192
|
+
export const detectConflictsSchema = z.object({
|
|
193
|
+
projectId: z.string().uuid(),
|
|
194
|
+
translations: z
|
|
195
|
+
.array(
|
|
196
|
+
z.object({
|
|
197
|
+
keyId: z.string().uuid(),
|
|
198
|
+
languageCode: z.string().min(1),
|
|
199
|
+
value: z.string(),
|
|
200
|
+
baselineValue: z.string(),
|
|
201
|
+
lastModified: z.string().datetime(),
|
|
202
|
+
}),
|
|
203
|
+
)
|
|
204
|
+
.optional(), // Optional - if not provided, will fetch from server
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Schema for resolving conflicts.
|
|
209
|
+
*/
|
|
210
|
+
export const resolveConflictsSchema = z.object({
|
|
211
|
+
projectId: z.string().uuid(),
|
|
212
|
+
resolutions: z.array(
|
|
213
|
+
z.object({
|
|
214
|
+
keyId: z.string().uuid(),
|
|
215
|
+
languageCode: z.string().min(1),
|
|
216
|
+
resolution: z.enum(["local", "server", "merged"]),
|
|
217
|
+
mergedValue: z.string().optional(), // Required if resolution is "merged"
|
|
218
|
+
}),
|
|
219
|
+
),
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Schema for getting translation health statistics.
|
|
224
|
+
*/
|
|
225
|
+
export const getHealthStatsSchema = z.object({
|
|
226
|
+
projectId: z.string().uuid(),
|
|
227
|
+
repositoryId: z.string().uuid().optional(),
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
export type GenerateSuggestionInput = z.infer<typeof generateSuggestionSchema>;
|
|
231
|
+
export type AiSuggestInput = z.infer<typeof aiSuggestSchema>;
|
|
232
|
+
export type PublishTranslationsInput = z.infer<
|
|
233
|
+
typeof publishTranslationsSchema
|
|
234
|
+
>;
|
|
235
|
+
export type BatchSyncTranslationsInput = z.infer<
|
|
236
|
+
typeof batchSyncTranslationsSchema
|
|
237
|
+
>;
|
|
238
|
+
export type DetectConflictsInput = z.infer<typeof detectConflictsSchema>;
|
|
239
|
+
export type ResolveConflictsInput = z.infer<typeof resolveConflictsSchema>;
|
|
240
|
+
export type GetHealthStatsInput = z.infer<typeof getHealthStatsSchema>;
|