use core::ops::Range;
use serde::{Deserialize, Serialize};
use crate::translation_error::TranslationError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct TranslationAttributes;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct TranslationAttributesEncodingConfiguration;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct TranslationAttributesDecodingConfiguration;
impl TranslationAttributes {
#[must_use]
pub const fn translation() -> Self {
Self
}
#[must_use]
pub const fn encoding_configuration() -> TranslationAttributesEncodingConfiguration {
TranslationAttributesEncodingConfiguration
}
#[must_use]
pub const fn decoding_configuration() -> TranslationAttributesDecodingConfiguration {
TranslationAttributesDecodingConfiguration
}
#[must_use]
pub const fn skips_translation(value: SkipTranslationAttributeValue) -> SkipTranslationAttribute {
SkipTranslationAttribute::new(value)
}
}
pub type SkipTranslationAttributeValue = bool;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
#[serde(transparent)]
pub struct SkipTranslationAttribute(SkipTranslationAttributeValue);
impl SkipTranslationAttribute {
pub const NAME: &str = "Translation.DoNotTranslate";
#[must_use]
pub const fn new(value: SkipTranslationAttributeValue) -> Self {
Self(value)
}
#[must_use]
pub const fn value(self) -> SkipTranslationAttributeValue {
self.0
}
#[must_use]
pub const fn enabled() -> Self {
Self(true)
}
#[must_use]
pub const fn disabled() -> Self {
Self(false)
}
}
impl From<SkipTranslationAttributeValue> for SkipTranslationAttribute {
fn from(value: SkipTranslationAttributeValue) -> Self {
Self::new(value)
}
}
impl From<SkipTranslationAttribute> for SkipTranslationAttributeValue {
fn from(value: SkipTranslationAttribute) -> Self {
value.value()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TranslationAttributedRun {
start: usize,
end: usize,
value: SkipTranslationAttribute,
}
impl TranslationAttributedRun {
#[must_use]
pub const fn new(start: usize, end: usize, value: SkipTranslationAttribute) -> Self {
Self { start, end, value }
}
#[must_use]
pub const fn start(&self) -> usize {
self.start
}
#[must_use]
pub const fn end(&self) -> usize {
self.end
}
#[must_use]
pub fn range(&self) -> Range<usize> {
self.start..self.end
}
#[must_use]
pub const fn value(&self) -> SkipTranslationAttribute {
self.value
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct TranslationAttributedString {
text: String,
#[serde(default)]
skip_translation_runs: Vec<TranslationAttributedRun>,
}
impl TranslationAttributedString {
#[must_use]
pub fn new(text: impl Into<String>) -> Self {
Self {
text: text.into(),
skip_translation_runs: Vec::new(),
}
}
#[must_use]
pub fn text(&self) -> &str {
&self.text
}
#[must_use]
pub fn skip_translation_runs(&self) -> &[TranslationAttributedRun] {
&self.skip_translation_runs
}
pub fn set_text(&mut self, text: impl Into<String>) {
self.text = text.into();
self.skip_translation_runs.clear();
}
pub fn add_skip_translation_run(
&mut self,
range: Range<usize>,
value: impl Into<SkipTranslationAttribute>,
) -> Result<(), TranslationError> {
self.validate_range(&range)?;
self.skip_translation_runs
.push(TranslationAttributedRun::new(range.start, range.end, value.into()));
self.skip_translation_runs.sort_by_key(TranslationAttributedRun::start);
Ok(())
}
pub fn add_skip_translation(
&mut self,
range: Range<usize>,
) -> Result<(), TranslationError> {
self.add_skip_translation_run(range, SkipTranslationAttribute::enabled())
}
pub fn with_skip_translation(
mut self,
range: Range<usize>,
) -> Result<Self, TranslationError> {
self.add_skip_translation(range)?;
Ok(self)
}
pub fn with_skip_translation_value(
mut self,
range: Range<usize>,
value: impl Into<SkipTranslationAttribute>,
) -> Result<Self, TranslationError> {
self.add_skip_translation_run(range, value)?;
Ok(self)
}
pub fn add_skip_translation_for_substring(
&mut self,
substring: &str,
) -> Result<(), TranslationError> {
let range = self.substring_range(substring)?;
self.add_skip_translation(range)
}
pub fn with_skip_translation_for_substring(
mut self,
substring: &str,
) -> Result<Self, TranslationError> {
self.add_skip_translation_for_substring(substring)?;
Ok(self)
}
fn validate_range(&self, range: &Range<usize>) -> Result<(), TranslationError> {
let character_len = self.text.chars().count();
if range.start > range.end || range.end > character_len {
return Err(TranslationError::InvalidArgument(format!(
"attributed text range {}..{} is outside 0..{}",
range.start, range.end, character_len
)));
}
Ok(())
}
fn substring_range(&self, substring: &str) -> Result<Range<usize>, TranslationError> {
if substring.is_empty() {
return Err(TranslationError::InvalidArgument(
"skip-translation substring must be non-empty".to_owned(),
));
}
let start_byte = self.text.find(substring).ok_or_else(|| {
TranslationError::InvalidArgument(format!(
"substring '{substring}' was not found in attributed text"
))
})?;
let end_byte = start_byte + substring.len();
Ok(self.text[..start_byte].chars().count()..self.text[..end_byte].chars().count())
}
}
impl From<String> for TranslationAttributedString {
fn from(text: String) -> Self {
Self::new(text)
}
}
impl From<&str> for TranslationAttributedString {
fn from(text: &str) -> Self {
Self::new(text)
}
}