我的iOS应用在不同的本地化中显示不同的货币(美元,日元,澳元,欧元)(en_US
,en_AU
,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获得正确的价值)。
答案 0 :(得分:3)
编辑:最新要点:https://gist.github.com/fjolnir/cd72ea39be1476023adf
旧线程,但我在寻找解决方案时遇到了它,所以我想我会发布我的实现。
格式化程序本身不处理placement的放置,但这很容易在它之外进行。 (如下例所示)
以下预期产出为:
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]);
}
}
答案 1 :(得分:1)
很棒的问题。
我猜“男人”的价格符号类似于欧洲的“K”符号(但这并不常见)。从这个意义上说,我猜一些“短价格/数字”符号没有标准,因此它不包含在标准文化格式化器和格式说明符中。我认为标准面向一些合理的共同点,万円
不适合那里。另外,在前面使用标准¥
符号时很奇怪,而在价格值之后使用円
。
标准是相当严格的东西,所以我不会考虑在不久的将来支持万円
式货币支持。所以,我认为现在只有“手动”的解决方案。
P.S。我想应该有一些第三方库可用,因为它必须是一个非常常见的任务。
答案 2 :(得分:1)
最后,我将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);
}
}