11

en_US私の iOS アプリでは、さまざまなローカライズ ( 、en_AU、など)でさまざまな通貨 (USD、JPY、AUD、EUR) が表示されますja_JP

日本語の地域/言語 (両方とも私のデバイスに設定されています) の場合:

NSNumberFormatter *fmt = [[NSNumberFormatter alloc] init];
fmt.numberStyle = NSNumberFormatterCurrencyStyle;
fmt.currencyCode = @"JPY";
NSString *labelText = [fmt stringFromNumber:@1000000];

私のラベル テキストは¥1,000,000. ただし、日本語と中国語では、10,000 を超える数字が書かれている可能性があり100万円、これは私が望む出力です。

100万円出力として 取得するためにどのコードを書くことができるか考えていますか?

ロケール/地域をチェックするコード内のロジック ブロックを避けたいのですが、それが問題になっているように感じます (たとえば、メソッド呼び出しfmt.multipler = @(1/10000)を使用して 1,000,000 を 10,000 で割り、正しい値を取得します)。

4

3 に答える 3

4

編集: ここで最新の要点: https://gist.github.com/fjolnir/cd72ea39be1476023adf

古いスレッドですが、解決策を探しているときに見つけたので、実装を投稿することにしました。

フォーマッター自体は円の配置を処理しませんが、円の外で簡単に行うことができます。(以下の例が示すように)

以下の期待される出力は次のとおりです。

2015-03-11 18:00:13.376 LENumberFormatter[82736:3604947] 12億3,460万円
2015-03-11 18:00:13.377 LENumberFormatter[82736:3604947] 25円

-

@import Foundation;
@import ObjectiveC.message;

typedef NS_ENUM(NSUInteger, LENumberFormatterAbbreviationStyle) {
    kLEAbbreviateShort, // 2.5m
    kLEAbbreviateNormal // 2m 5k
};

@interface LENumberFormatter : NSNumberFormatter
@property(nonatomic) BOOL abbreviateLargeNumbers;
@property(nonatomic) LENumberFormatterAbbreviationStyle abbreviationStyle;
@end


@implementation LENumberFormatter
- (instancetype)init
{
    if((self = [super init])) {
        self.abbreviationStyle = [self _usingKanjiNumbers]
                               ? kLEAbbreviateNormal
                               : kLEAbbreviateShort;
    }
    return self;
}

- (NSString *)stringForObjectValue:(id const)aObj
{
    if(!_abbreviateLargeNumbers || ![aObj isKindOfClass:[NSNumber class]])
        return [super stringForObjectValue:aObj];

    // Copy ourselves to get format the partial digits using the settings on self
    LENumberFormatter * const partialFormatter = [self copy];
    partialFormatter.currencySymbol = @"";
    if(_abbreviationStyle == kLEAbbreviateNormal)
        partialFormatter.maximumFractionDigits = 0;

    NSString *(^partialFormat)(NSNumber*) = ^(NSNumber *num) {
        NSString *(*superImp)(struct objc_super*,SEL,NSNumber*) = (void*)&objc_msgSendSuper;
        return superImp(&(struct objc_super) { partialFormatter, self.superclass }, _cmd, num);
    };

    double n = [aObj doubleValue];
    BOOL const shortFormat = _abbreviationStyle == kLEAbbreviateShort;

    NSDictionary * const separators         = [self _localizedGroupingSeparators];
    NSArray      * const separatorExponents = [separators.allKeys sortedArrayUsingSelector:@selector(compare:)];

    BOOL const currencySymbolIsSuffix = [self.positiveFormat hasSuffix:@"¤"];
    NSMutableString * const result = currencySymbolIsSuffix || self.numberStyle != NSNumberFormatterCurrencyStyle
                                   ? [NSMutableString new]
                                   : [self.currencySymbol mutableCopy];
    NSUInteger significantDigits = 0;
    NSNumber *lastExp = nil;
    for(NSNumber *exp in separatorExponents.reverseObjectEnumerator) {
        double divisor = pow(10, exp.shortValue);
        if(divisor > n)
            continue;

        if(lastExp)
            significantDigits += lastExp.doubleValue - exp.doubleValue;
        lastExp = exp;

        if(self.usesSignificantDigits && significantDigits >= self.maximumSignificantDigits)
            break;

        double partialNum = shortFormat
                          ? n/divisor
                          : floor(n/divisor);
        NSString * const digits = [self _groupRecursively] && ![exp isEqual:@0]
                                ? [partialFormatter stringFromNumber:@(partialNum)]
                                : partialFormat(@(partialNum));
        [result appendFormat:@"%@%@", digits, separators[exp]];

        n = fmod(n, divisor);

        if(shortFormat)
            break; // Just use a float+first hit

        // If we make it here, partialNum is integral and we can use log10 to find the number of digits
        significantDigits += log10(partialNum) + 1;
        partialFormatter.maximumSignificantDigits -= digits.length;

    }
    if(n > 0
       && !shortFormat
       && (!self.usesSignificantDigits || significantDigits < self.maximumSignificantDigits))
    {
        partialFormatter.maximumFractionDigits = self.maximumFractionDigits;
        [result appendString:partialFormat(@(n))];
    }
    if(self.numberStyle == NSNumberFormatterCurrencyStyle && currencySymbolIsSuffix && self.currencySymbol)
        [result appendString:self.currencySymbol];

    return result.length > 0
         ? [result stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet]
         : [super stringForObjectValue:aObj];
}

- (BOOL)_usingKanjiNumbers
{
    return [self.locale.localeIdentifier rangeOfString:@"^(ja|zh)_"
                                               options:NSRegularExpressionSearch].location != NSNotFound;
}
- (NSDictionary *)_localizedGroupingSeparators
{
    if(self._usingKanjiNumbers)
        return @{ @2: @"百", @3: @"千", @4: @"万", @8: @"億" };
    else {
        NSBundle * const bundle = [NSBundle bundleForClass:self.class];
        return @{ 
            @3: [bundle localizedStringForKey:@"thousandSuffix" value:@"k " table:nil],
            @6: [bundle localizedStringForKey:@"millionSuffix"  value:@"m " table:nil]
        };
    }
}

- (BOOL)_groupRecursively
{
    // Return _usingKanjiNumbers if you want:
    // 12億3千4百56万7千8百90
    // Rather than:
    // 1億2,3456万7千8百90
    return NO;
}

- (instancetype)copyWithZone:(NSZone * const)aZone
{
    LENumberFormatter * const copy = [super copyWithZone:aZone];
    copy.abbreviateLargeNumbers = _abbreviateLargeNumbers;
    copy.abbreviationStyle      = _abbreviationStyle;
    return copy;
}
@end


int main(int argc, char *argv[]) {
    @autoreleasepool {
        LENumberFormatter * const f = [LENumberFormatter new];
        f.locale = [NSLocale localeWithLocaleIdentifier:@"ja_JP"];
//        f.locale = [NSLocale localeWithLocaleIdentifier:@"en_US"];
        f.numberStyle = NSNumberFormatterCurrencyStyle;
        f.abbreviateLargeNumbers = YES;
        f.abbreviationStyle = kLEAbbreviateNormal; // Automatic if using system locale
        f.maximumSignificantDigits = 5;
        f.usesSignificantDigits = YES;
//        f.currencyCode   = @"JPY";
//        f.currencySymbol = @"¥";

        if([f.locale.localeIdentifier hasPrefix:@"ja"]) {
            f.positiveFormat = @"#,##0¤";
            if([f.currencyCode isEqualToString:@"JPY"])
                // We allow ourselves this special case because *日本円 just looks dumb
                f.currencySymbol = @"円";
            else
                f.currencySymbol = [f.locale displayNameForKey:NSLocaleCurrencyCode
                                          value:f.currencyCode];
        }

        NSLog(@"%@", [f stringFromNumber:@1234567890]);
        NSLog(@"%@", [f stringFromNumber:@25]);
    }
}
于 2015-03-11T09:06:44.887 に答える
2

結局、私はサブクラス化NSNumberFormatterして上書きしstringWithNumber:ました。

NSNumberFormatter通貨コードがの場合に再構成するために使用した関連コードは次のとおりですJPY

  NSString *localeString = [self.locale localeIdentifier];
  if ([localeString isEqualToString:@"ja_JP"])
  {
    // 1-oku
    if (num >= 100000000)
    {
      self.negativeFormat = @"-#,###0億円";
      self.positiveFormat = @"#,###0億円";
      self.multiplier = @(1.0f/100000000.0f);
    }
    // 1-man
    else if (num >= 10000)
    {
      self.negativeFormat = @"-#,###0万円";
      self.positiveFormat = @"#,###0万円";
      self.multiplier = @(1.0f/10000.0f);
    }
    // Less than 10,000
    else
    {
      self.negativeFormat = @"-#,###0円";
      self.positiveFormat = @"#,###0円";
    }
  }
  // This could be en_AU, en_UK, en_US -- but all use "million yen"
  else if ([localeString hasPrefix:@"en"])
  {
    // We only care about 1M JPY+
    if (num >= 1000000)
    {
      self.negativeFormat = @"-¥#,###0M";
      self.positiveFormat = @"¥#,###0M";
      self.multiplier = @(1.0f/1000000.0f);
    }
  }
于 2013-01-23T06:59:24.580 に答える
1
于 2013-01-23T03:04:32.650 に答える