Skip to main content

xcstrings_mcp/model/
translation.rs

1use std::collections::BTreeMap;
2
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5
6/// A string needing translation, returned by get_untranslated, get_stale, and search_keys.
7#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
8pub struct TranslationUnit {
9    /// Localization key name
10    pub key: String,
11    /// Source language text to translate from
12    pub source_text: String,
13    /// Locale code this unit needs translation for
14    pub target_locale: String,
15    /// Developer comment providing context for translators
16    #[serde(default, skip_serializing_if = "Option::is_none")]
17    pub comment: Option<String>,
18    /// Format specifiers found in source (e.g., ["%@", "%lld"]). Translations MUST preserve these exactly.
19    pub format_specifiers: Vec<String>,
20    /// True if key uses plural variations. Use get_plurals for full details before translating.
21    pub has_plurals: bool,
22    /// True if key uses substitution variables (%#@VAR@). Use get_plurals for details.
23    pub has_substitutions: bool,
24}
25
26/// A completed translation to submit via submit_translations.
27#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
28pub struct CompletedTranslation {
29    /// Localization key exactly as returned by get_untranslated or get_plurals
30    pub key: String,
31    /// Target locale code (e.g., "uk", "de"). Must not be the source language.
32    pub locale: String,
33    /// Translated text for simple strings. Must preserve all format specifiers from source. Ignored when plural_forms is set.
34    pub value: String,
35    /// Plural translations keyed by CLDR category, e.g. {"one": "1 item", "other": "%lld items"}. Required categories vary by locale — use get_plurals to see which forms are needed. When set, value is ignored.
36    #[serde(default, skip_serializing_if = "Option::is_none")]
37    pub plural_forms: Option<BTreeMap<String, String>>,
38    /// Substitution variable name for multi-variable plurals (from %#@VAR@ in source). Only needed when PluralUnit.has_substitutions is true. Omit for simple plurals.
39    #[serde(default, skip_serializing_if = "Option::is_none")]
40    pub substitution_name: Option<String>,
41}
42
43/// Summary of a parsed .xcstrings file, returned by parse_xcstrings.
44#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
45pub struct FileSummary {
46    /// Source language code (e.g., "en")
47    pub source_language: String,
48    /// Total number of keys in the file (including non-translatable)
49    pub total_keys: usize,
50    /// Number of keys that should be translated (shouldTranslate=true)
51    pub translatable_keys: usize,
52    /// All locale codes present in the file
53    pub locales: Vec<String>,
54    /// Key count per extraction state (e.g., {"extracted_with_value": 42, "manual": 3})
55    pub keys_by_state: BTreeMap<String, usize>,
56}
57
58/// Result of submit_translations or import_xliff.
59#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
60pub struct SubmitResult {
61    /// Number of translations that passed validation and were written (or would be written in dry_run)
62    pub accepted: usize,
63    /// Translations that failed validation. Check reason field for details, fix, and resubmit.
64    pub rejected: Vec<RejectedTranslation>,
65    /// True if this was a validation-only run (nothing written to disk)
66    pub dry_run: bool,
67    /// List of accepted key names for reference
68    #[serde(default, skip_serializing_if = "Vec::is_empty")]
69    pub accepted_keys: Vec<String>,
70}
71
72/// A translation that failed validation during submit.
73#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
74pub struct RejectedTranslation {
75    /// The key that was rejected
76    pub key: String,
77    /// Human-readable rejection reason (e.g., missing format specifier, wrong plural forms)
78    pub reason: String,
79}
80
81/// Per-locale translation coverage statistics.
82#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
83pub struct LocaleCoverage {
84    /// Locale code
85    pub locale: String,
86    /// Total keys in the file (including non-translatable)
87    pub total_keys: usize,
88    /// Number of keys that should be translated
89    pub translatable_keys: usize,
90    /// Number of keys with translations in this locale
91    pub translated: usize,
92    /// Translation completion percentage (0.0–100.0)
93    pub percentage: f64,
94}
95
96/// Full coverage report across all locales.
97#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
98pub struct CoverageReport {
99    pub source_language: String,
100    pub total_keys: usize,
101    pub translatable_keys: usize,
102    pub locales: Vec<LocaleCoverage>,
103}
104
105/// Validation result with errors and warnings for a single locale.
106#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
107pub struct ValidationReport {
108    pub locale: String,
109    pub errors: Vec<ValidationIssue>,
110    pub warnings: Vec<ValidationIssue>,
111}
112
113/// A single validation problem found in a translation.
114#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
115pub struct ValidationIssue {
116    /// Localization key with the issue
117    pub key: String,
118    /// Issue category (e.g., "missing_format_specifier", "wrong_plural_forms", "empty_value")
119    pub issue_type: String,
120    /// Human-readable description of the problem
121    pub message: String,
122}
123
124/// Locale info for list_locales output.
125#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
126pub struct LocaleInfo {
127    pub locale: String,
128    pub translated: usize,
129    pub total: usize,
130    pub percentage: f64,
131}
132
133/// A key requiring plural translation (returned by get_plurals).
134#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
135pub struct PluralUnit {
136    /// Localization key name
137    pub key: String,
138    /// Source language text
139    pub source_text: String,
140    /// Locale code to translate for
141    pub target_locale: String,
142    /// Developer comment for context
143    #[serde(default, skip_serializing_if = "Option::is_none")]
144    pub comment: Option<String>,
145    /// Format specifiers to preserve in all plural forms (e.g., ["%lld"])
146    pub format_specifiers: Vec<String>,
147    /// Required CLDR plural categories for target locale (e.g., ["one", "few", "many", "other"] for Ukrainian). All forms must be provided in submit_translations.
148    pub required_forms: Vec<String>,
149    /// Source language plural forms (if available).
150    pub source_forms: BTreeMap<String, String>,
151    /// Existing translations per plural form (if partially translated).
152    pub existing_translations: BTreeMap<String, String>,
153    /// True if this key uses substitutions (%#@VAR@).
154    pub has_substitutions: bool,
155    /// Device variant forms needed (if any).
156    #[serde(default, skip_serializing_if = "Vec::is_empty")]
157    pub device_forms: Vec<String>,
158}
159
160/// A nearby key sharing a common prefix, used for translator context.
161#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
162pub struct ContextKey {
163    /// Related key name
164    pub key: String,
165    /// Source language text
166    pub source_text: String,
167    /// Existing translation in the target locale, if available
168    #[serde(default, skip_serializing_if = "Option::is_none")]
169    pub translated_text: Option<String>,
170}
171
172/// Report of differences between cached and on-disk versions.
173#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
174pub struct DiffReport {
175    /// Keys added to the file since last parse
176    pub added: Vec<String>,
177    /// Keys removed from the file since last parse
178    pub removed: Vec<String>,
179    /// Keys whose source language text changed
180    pub modified: Vec<ModifiedKey>,
181}
182
183/// A key whose source text changed between cached and on-disk versions.
184#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
185pub struct ModifiedKey {
186    /// Localization key name
187    pub key: String,
188    /// Previous source text (from cache)
189    pub old_value: String,
190    /// Current source text (from disk)
191    pub new_value: String,
192}