I have a UILabel with an attributed string. Here is a printscreen of it:

enter image description here

Now, I have to translate this attributed string to english and italian. I am searching for a way to do this. Can I build this attributed string in code part by part? I have only found a solution where the whole string is set and then the attributes are set by range. But when I translate the string, I don't know the range anymore, because the words are longer or smaller.

有帮助吗?

解决方案

Another option is to create localized .rtf files from which to create NSAttributedStrings:

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

To use dynamic formatting (e.g. set colors, fonts specific to some app settings), I'm using some html-like formatting, with 1-char tags for which I afterwards apply the format from inside the app.

// 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];
    }
}

The method could afterwards be used like this:

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

Where attrStr may be @"Press <b>Next</b> button to ...".

其他提示

Something like this method could work. It takes an NSAttributedString, extracts parts based on their attributes, translates each part, applies the same attributes and finally returns the complete translated attributed string.

-(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;
}

You can take advantage of the fact that you can construct an attributed string from HTML data. So write your string with embedded HTML attributes in Localized.string :

MY_STRING = "some <b>bold</b> string";

then you can retrieve it like this

    let htmlTitle = NSLocalizedString("MY_STRING", comment: "")
    let data = htmlTitle.data(using: .utf8)!
    let attributedTitle = NSAttributedString(html: data, options:[:], documentAttributes: nil)
    myLabel.attributedText = attributedTitle!

Note that you'll have to do a bit more work with the attributes to set the proper font, as it will use the default font for html display (Helvetica ?).

I would solve this by translating the individual parts of the string. This will work in this case because your attributed string really is a concatenated version of four strings.

But make sure that you save the format where you use numbers. In some language the text might be "3 Erweiterung". You can do this by using 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

This way you get a nice strings file you can use to translate:

/* '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";

I usually translate highlighted parts separately and add unique placeholders to the original text which is easy to find and replace.

Example for: "This word is bold"

In localized.strings it will be
"BaseText" = "This -Word- is bold";
"HighlightedText" = "word";

Now we can take the original string with "BaseText" key and:
1. Find the range of "-Word-" substring
2. Replace it with localized string with "HighlightedText" key.
3. Using the original placeholder's range, original placeholder's length and the translation length you can easily calculate new range and apply specific attributes to it.

This approach is more flexible than concatenation and does not depend on the words order in a translation.

This is an extension for it:

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)
    }
}

You can replace several placeholders one after another if needed.

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top