Перевести присланную строку

У меня есть UILabel с атрибутной строкой. Вот его печатный экран:

enter image description here

Теперь я должен перевести эту атрибутную строку на английский и итальянский. Я ищу способ сделать это. Могу ли я построить эту атрибутивную строку в части кода по частям? Я нашел решение, в котором установлена ​​вся строка, а затем атрибуты задаются диапазоном. Но когда я переводил строку, я больше не знаю диапазон, потому что слова длиннее или меньше.

Ответ 1

Другой вариант - создать локализованные .rtf файлы, из которых можно создать NSAttributedStrings:

NSAttributedString *attributedStr = [[NSAttributedString alloc] initWithData:data options:@{NSDocumentTypeDocumentAttribute:NSRTFTextDocumentType} documentAttributes:nil error:nil];

Чтобы использовать динамическое форматирование (например, установить цвета, шрифты, характерные для некоторых параметров приложения), я использую какое-то html-форматирование с тегами 1- char, для которых я впоследствии применяю формат изнутри приложения.

// NSMutableAttributedString category method
/**
 * Updates the attributes of xml elements (where the xml tags are formed of 1 single char) with the passed attributes from param `tagsAttributes`
 * Current version doesn't support recursive tags (tags in tags)
 * All tags of form '<char>' or '</char>' will be used as formatting (the resulting string should not be expected to have any tags of this form)
 * @param tagsAttributes - list of attribute dictionaries, where the key is the tag name */
-(void)formatCharXMLTagsUsingAttributes:(NSDictionary *)tagsAttributes {
    int strippedLength = 0;

    NSString *str = [[self string] copy];
    NSScanner *scanner = [NSScanner scannerWithString:str];
    while (![scanner isAtEnd]) {
        NSString *tag = nil;
        do {
            [scanner scanUpToString:@"<" intoString:nil];
            [scanner scanString:@"<" intoString:nil];
            if (scanner.scanLocation + 2 < [str length] && [str characterAtIndex:scanner.scanLocation + 1] == '>') {
                [scanner scanUpToString:@">" intoString:&tag];
                [scanner scanString:@">" intoString:nil];
            }
        } while (!tag && ![scanner isAtEnd]);

        if ([scanner isAtEnd]) {
            break;
        }

        NSString *endTag = [NSString stringWithFormat:@"</%@>", tag];
        NSString *tmpString;
        [scanner scanUpToString:endTag intoString:&tmpString];
        [scanner scanString:endTag intoString:nil];
        NSRange range;
        strippedLength += 7; // start tag + end tag length
        range.location = scanner.scanLocation - [tmpString length] - strippedLength;
        range.length = [tmpString length] + 7;
        [self replaceCharactersInRange:range withString:tmpString];
        range.length -= 7;
        [self addAttributes:tagsAttributes[tag] range:range];
    }
}

После этого метод можно использовать следующим образом:

NSDictionary* highlightAttributes = @{NSForegroundColorAttributeName: [UIColor blueColor],
                                 NSFontAttributeName: [UIFont boldSystemFontOfSize:16]};
NSDictionary *xmlTagsAttributes = @{@"b": highlightAttributes};
[attrStr formatCharXMLTagsUsingAttributes:xmlTagsAttributes];

Где attrStr может быть @"Press <b>Next</b> button to ...".

Ответ 2

Что-то вроде этого метода может работать. Он принимает NSAttributedString, извлекает части на основе их атрибутов, переводит каждую часть, применяет одни и те же атрибуты и, наконец, возвращает полную переведенную атрибутную строку.

-(NSAttributedString*)translateAttribString:(NSAttributedString*)attribString toLanguage:(NSString*)language
{
    NSMutableAttributedString *returnString = [[NSMutableAttributedString alloc]init];

    NSRange totalRange = NSMakeRange (0, attribString.length);

    [attribString enumerateAttributesInRange: totalRange options: 0 usingBlock: ^(NSDictionary *attributes, NSRange range, BOOL *stop)
     {
         NSLog (@"range: %@ attributes: %@", NSStringFromRange(range), attributes);

         NSString *string = [[attribString string] substringWithRange:range];

         NSLog(@"string at range %@", string);

         //Translate 'string' based on 'language' here.

         NSString *trans; //This will hold the translated string.

         NSAttributedString *translatedString = [[NSAttributedString alloc]initWithString:trans attributes:attributes];

         [returnString appendAttributedString:translatedString];

     }];

    return returnString;
}

Ответ 3

Я бы решил это, переведя отдельные части строки. Это будет работать в этом случае, потому что ваша атрибутная строка действительно представляет собой объединенную версию из четырех строк.

Но убедитесь, что вы сохранили формат, в котором используете номера. На некотором языке текст может быть "3 Erweiterung". Вы можете сделать это, используя NSLocalizedStringWithDefaultValue.

NSString *stepFormat = NSLocalizedStringWithDefaultValue(@"AttributedStringStepFormat", @"main", [NSBundle mainBundle], @"Step %ld", @"'Step 4' in 'Step 4 - Erweiterung 3 - erhalten\ndauerhaft'");
NSString *step = [NSString stringWithFormat:stepFormat, (long)4];

NSString *erweiterungFormat = NSLocalizedStringWithDefaultValue(@"AttributedStringErweiterungFormat", @"main", [NSBundle mainBundle], @"Erweiterung %ld", @"'Erweiterung 3' in 'Step 4 - Erweiterung 3 - erhalten\ndauerhaft'");
NSString *erweiterung = [NSString stringWithFormat:erweiterungFormat, (long)3];

NSString *erhalten = NSLocalizedStringWithDefaultValue(@"AttributedStringErhalten", @"main", [NSBundle mainBundle], @"erhalten", @"'erhalten' in 'Step 4 - Erweiterung 3 - erhalten\ndauerhaft'");
NSString *dauerhaft = NSLocalizedStringWithDefaultValue(@"AttributedStringDauerhaft", @"main", [NSBundle mainBundle], @"dauerhaft", @"'dauerhaft' in 'Step 4 - Erweiterung 3 - erhalten\ndauerhaft'");

NSString *result = [NSString stringWithFormat:@"%@ - %@ - %@\n%@", step, erweiterung, erhalten, dauerhaft];

NSRange stepRange = [result rangeOfString:step];
NSRange erweiterungRange = [result rangeOfString:erweiterung];
NSRange erhaltenRange = [result rangeOfString:erhalten];
NSRange dauerhaftRange = [result rangeOfString:dauerhaft];

// Create attributed string

Таким образом вы получите файл с хорошими строками, который вы можете использовать для перевода:

/* 'dauerhaft' in 'Step 4 - Erweiterung 3 - erhalten\ndauerhaft' */
"AttributedStringDauerhaft" = "dauerhaft";

/* 'erhalten' in 'Step 4 - Erweiterung 3 - erhalten\ndauerhaft' */
"AttributedStringErhalten" = "erhalten";

/* 'Erweiterung 3' in 'Step 4 - Erweiterung 3 - erhalten\ndauerhaft' */
"AttributedStringErweiterungFormat" = "Erweiterung %ld";

/* 'Step 4' in 'Step 4 - Erweiterung 3 - erhalten\ndauerhaft' */
"AttributedStringStepFormat" = "Step %ld";

Ответ 4

Я обычно переводил выделенные части отдельно и добавлял уникальные заполнители к исходному тексту, который легко найти и заменить.

Пример для: "Это слово выделено жирным шрифтом"

В localized.strings это будет
"BaseText" = "Это -Word- жирный шрифт",
"HighlightedText" = "word";

Теперь мы можем взять исходную строку с ключом "BaseText" и:
1. Найдите диапазон подстроки "-Word-"
2. Замените его на локализованную строку клавишей "HighlightedText".
3. Используя исходный размер заполнителя, исходную длину заполнителя и длину перевода, вы можете легко вычислить новый диапазон и применить к нему определенные атрибуты.

Этот подход более гибкий, чем конкатенация, и не зависит от порядка слов в переводе.

Это расширение для него:

extension NSMutableAttributedString {
    func replacePlaceholder(_ placeholder: String, with translation: String, attributes: [String: Any]) {

        // find the placeholder
        var range = (string as NSString).range(of: placeholder)

        // nothing to replace
        if range.location == NSNotFound {
            return
        }

        // replace it with the translation
        mutableString.replaceCharacters(in: range, with: translation)

        // adjust range according to changes
        range.length = range.length + translation.length - placeholder.length

        // apply attributes
        self.setAttributes(attributes, range: range)
    }
}

При необходимости вы можете заменить несколько заполнителей один за другим.