translation/
translation_attributes.rs1use core::ops::Range;
2
3use serde::{Deserialize, Serialize};
4
5use crate::translation_error::TranslationError;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
9pub struct TranslationAttributes;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
13pub struct TranslationAttributesEncodingConfiguration;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
17pub struct TranslationAttributesDecodingConfiguration;
18
19impl TranslationAttributes {
20 #[must_use]
21 pub const fn translation() -> Self {
23 Self
24 }
25
26 #[must_use]
27 pub const fn encoding_configuration() -> TranslationAttributesEncodingConfiguration {
29 TranslationAttributesEncodingConfiguration
30 }
31
32 #[must_use]
33 pub const fn decoding_configuration() -> TranslationAttributesDecodingConfiguration {
35 TranslationAttributesDecodingConfiguration
36 }
37
38 #[must_use]
39 pub const fn skips_translation(value: SkipTranslationAttributeValue) -> SkipTranslationAttribute {
41 SkipTranslationAttribute::new(value)
42 }
43}
44
45pub type SkipTranslationAttributeValue = bool;
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
50#[serde(transparent)]
51pub struct SkipTranslationAttribute(SkipTranslationAttributeValue);
52
53impl SkipTranslationAttribute {
54 pub const NAME: &str = "Translation.DoNotTranslate";
56
57 #[must_use]
58 pub const fn new(value: SkipTranslationAttributeValue) -> Self {
60 Self(value)
61 }
62
63 #[must_use]
64 pub const fn value(self) -> SkipTranslationAttributeValue {
66 self.0
67 }
68
69 #[must_use]
70 pub const fn enabled() -> Self {
72 Self(true)
73 }
74
75 #[must_use]
76 pub const fn disabled() -> Self {
78 Self(false)
79 }
80}
81
82impl From<SkipTranslationAttributeValue> for SkipTranslationAttribute {
83 fn from(value: SkipTranslationAttributeValue) -> Self {
84 Self::new(value)
85 }
86}
87
88impl From<SkipTranslationAttribute> for SkipTranslationAttributeValue {
89 fn from(value: SkipTranslationAttribute) -> Self {
90 value.value()
91 }
92}
93
94#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
96#[serde(rename_all = "camelCase")]
97pub struct TranslationAttributedRun {
98 start: usize,
99 end: usize,
100 value: SkipTranslationAttribute,
101}
102
103impl TranslationAttributedRun {
104 #[must_use]
105 pub const fn new(start: usize, end: usize, value: SkipTranslationAttribute) -> Self {
107 Self { start, end, value }
108 }
109
110 #[must_use]
111 pub const fn start(&self) -> usize {
113 self.start
114 }
115
116 #[must_use]
117 pub const fn end(&self) -> usize {
119 self.end
120 }
121
122 #[must_use]
123 pub fn range(&self) -> Range<usize> {
125 self.start..self.end
126 }
127
128 #[must_use]
129 pub const fn value(&self) -> SkipTranslationAttribute {
131 self.value
132 }
133}
134
135#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
137#[serde(rename_all = "camelCase")]
138pub struct TranslationAttributedString {
139 text: String,
140 #[serde(default)]
141 skip_translation_runs: Vec<TranslationAttributedRun>,
142}
143
144impl TranslationAttributedString {
145 #[must_use]
146 pub fn new(text: impl Into<String>) -> Self {
148 Self {
149 text: text.into(),
150 skip_translation_runs: Vec::new(),
151 }
152 }
153
154 #[must_use]
155 pub fn text(&self) -> &str {
157 &self.text
158 }
159
160 #[must_use]
161 pub fn skip_translation_runs(&self) -> &[TranslationAttributedRun] {
163 &self.skip_translation_runs
164 }
165
166 pub fn set_text(&mut self, text: impl Into<String>) {
168 self.text = text.into();
169 self.skip_translation_runs.clear();
170 }
171
172 pub fn add_skip_translation_run(
174 &mut self,
175 range: Range<usize>,
176 value: impl Into<SkipTranslationAttribute>,
177 ) -> Result<(), TranslationError> {
178 self.validate_range(&range)?;
179 self.skip_translation_runs
180 .push(TranslationAttributedRun::new(range.start, range.end, value.into()));
181 self.skip_translation_runs.sort_by_key(TranslationAttributedRun::start);
182 Ok(())
183 }
184
185 pub fn add_skip_translation(
187 &mut self,
188 range: Range<usize>,
189 ) -> Result<(), TranslationError> {
190 self.add_skip_translation_run(range, SkipTranslationAttribute::enabled())
191 }
192
193 pub fn with_skip_translation(
195 mut self,
196 range: Range<usize>,
197 ) -> Result<Self, TranslationError> {
198 self.add_skip_translation(range)?;
199 Ok(self)
200 }
201
202 pub fn with_skip_translation_value(
204 mut self,
205 range: Range<usize>,
206 value: impl Into<SkipTranslationAttribute>,
207 ) -> Result<Self, TranslationError> {
208 self.add_skip_translation_run(range, value)?;
209 Ok(self)
210 }
211
212 pub fn add_skip_translation_for_substring(
214 &mut self,
215 substring: &str,
216 ) -> Result<(), TranslationError> {
217 let range = self.substring_range(substring)?;
218 self.add_skip_translation(range)
219 }
220
221 pub fn with_skip_translation_for_substring(
223 mut self,
224 substring: &str,
225 ) -> Result<Self, TranslationError> {
226 self.add_skip_translation_for_substring(substring)?;
227 Ok(self)
228 }
229
230 fn validate_range(&self, range: &Range<usize>) -> Result<(), TranslationError> {
231 let character_len = self.text.chars().count();
232 if range.start > range.end || range.end > character_len {
233 return Err(TranslationError::InvalidArgument(format!(
234 "attributed text range {}..{} is outside 0..{}",
235 range.start, range.end, character_len
236 )));
237 }
238 Ok(())
239 }
240
241 fn substring_range(&self, substring: &str) -> Result<Range<usize>, TranslationError> {
242 if substring.is_empty() {
243 return Err(TranslationError::InvalidArgument(
244 "skip-translation substring must be non-empty".to_owned(),
245 ));
246 }
247 let start_byte = self.text.find(substring).ok_or_else(|| {
248 TranslationError::InvalidArgument(format!(
249 "substring '{substring}' was not found in attributed text"
250 ))
251 })?;
252 let end_byte = start_byte + substring.len();
253 Ok(self.text[..start_byte].chars().count()..self.text[..end_byte].chars().count())
254 }
255}
256
257impl From<String> for TranslationAttributedString {
258 fn from(text: String) -> Self {
259 Self::new(text)
260 }
261}
262
263impl From<&str> for TranslationAttributedString {
264 fn from(text: &str) -> Self {
265 Self::new(text)
266 }
267}