├── Default-568h@2x.png ├── JJPluralForm.podspec ├── JJPluralForm ├── JJPluralForm.h └── JJPluralForm.m ├── JJPluralFormExample.xcodeproj └── project.pbxproj ├── JJPluralFormExample ├── AppDelegate.h ├── AppDelegate.m ├── JJPluralFormExample-Info.plist ├── JJPluralFormExample-Prefix.pch ├── LocalizationViewController.h ├── LocalizationViewController.m ├── ar.lproj │ └── Localizable.strings ├── en.lproj │ ├── InfoPlist.strings │ └── Localizable.strings ├── main.m ├── ru.lproj │ └── Localizable.strings └── zh-Hans.lproj │ └── Localizable.strings ├── JJPluralForms.png ├── LICENSE └── README.md /Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junjie/JJPluralForm/bd051b253a2c0286c2f1c70a5901f56d4943a02f/Default-568h@2x.png -------------------------------------------------------------------------------- /JJPluralForm.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "JJPluralForm" 3 | s.version = "2.1" 4 | s.summary = "Helps localize strings such as 'N day' by returning '1 day', '2 days'" 5 | s.description = <<-DESC 6 | In English, a word can either be singular or plural (e.g. 1 day, 2 days, 10 days). 7 | Some languages like Chinese have only one form (eg. 1 天, 2 天, 10 天), while others 8 | like Russian have three (1 день, 2 дня, 10 дней). 9 | 10 | JJPluralForm is adapted from Mozilla's PluralForm to handle plural forms in your 11 | Objective-C projects. 12 | DESC 13 | s.homepage = "https://github.com/junjie/JJPluralForm" 14 | s.screenshots = "https://github.com/junjie/JJPluralForm/raw/master/JJPluralForms.png" 15 | s.license = { :type => 'MPL 2.0', :file => 'LICENSE' } 16 | s.author = { "Lin Junjie" => "mail.junjie@gmail.com" } 17 | s.social_media_url = "http://twitter.com/jjlin" 18 | s.source = { :git => "https://github.com/junjie/JJPluralForm.git", :tag => "2.1" } 19 | s.source_files = 'JJPluralForm', 'JJPluralForm/**/*.{h,m}' 20 | s.requires_arc = true 21 | end 22 | -------------------------------------------------------------------------------- /JJPluralForm/JJPluralForm.h: -------------------------------------------------------------------------------- 1 | // 2 | // JJPluralForm.h 3 | // JJPluralForm 2.0 4 | // 5 | // Created by Lin Junjie (Clean Shaven Apps Pte. Ltd.) on 22/2/14. 6 | // Copyright (c) 2014 Lin Junjie. All rights reserved. 7 | // 8 | // This Source Code Form is subject to the terms of the Mozilla Public 9 | // License, v. 2.0. If a copy of the MPL was not distributed with this 10 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 11 | 12 | #import 13 | 14 | /** 15 | * This is a convenience macro that can be use to obtain the plural rule 16 | * specified in the localization file by setting the string 'JJ_PLURAL_FORM_RULE' 17 | * to the appropriate rule in each of the Localizable.strings. 18 | */ 19 | #define kJJPluralFormRule NSLocalizedString(@"JJ_PLURAL_FORM_RULE", @"Set the string 'JJ_PLURAL_FORM_RULE' to the appropriate rule in each of the Localizable.strings") 20 | 21 | /** 22 | * A plural rule governing how words should change depending on the number 23 | * qualifying the word. In English, there are only two forms, singular (is 1) 24 | * or plural (everything else). Many Asian languages have only a single form, 25 | * while some like Arabic has six different forms. 26 | */ 27 | typedef NS_ENUM(NSUInteger, JJPluralRule) { 28 | JJPluralRuleAsian = 0, /*!< 1 form: 29 | 30 | - everything 31 | 32 | e.g. Chinese, Japanese, Korean, 33 | Vietnamese, Thai, Lao */ 34 | 35 | JJPluralRuleAsianExtended = 100, /*!< 3 forms. Extended rule #0 from 36 | 1 to 3 forms by introducing 'is 1' 37 | and 'is 2': 38 | 39 | - is 1 40 | 41 | - is 2 42 | 43 | - everything 44 | 45 | e.g. Chinese, Japanese, Korean, 46 | Vietnamese, Thai, Lao, Swedish */ 47 | 48 | 49 | JJPluralRuleEnglish = 1, /*!< 2 forms: 50 | 51 | - is 1, 52 | 53 | - everything else 54 | 55 | e.g. English, German, Dutch, Norwegian, 56 | Swedish, Italian, Portuguese, Spanish, 57 | etc. */ 58 | 59 | JJPluralRuleFrench = 2, /*!< 2 forms: 60 | 61 | - is 0 or 1, 62 | 63 | - everything else 64 | 65 | e.g. French, Brazilian Portuguese */ 66 | 67 | JJPluralRuleFrenchExtended = 102, /*!< 3 forms. Extended rule #2 from 68 | 2 to 3 forms by separating 'is 0 or 1' 69 | 70 | - is 0, 71 | 72 | - is 1, 73 | 74 | - everything else 75 | 76 | e.g. French, Brazilian Portuguese. 77 | Also recommended for languages in 78 | rule #1, eg. English, to distinguish 79 | between 0, 1 and everything else. */ 80 | 81 | JJPluralRuleLatvian = 3, /*!< 3 forms: 82 | 83 | - is 0, 84 | 85 | - ends in 1, excluding 11 86 | 87 | - everything else */ 88 | 89 | JJPluralRuleScottishGaelic = 4, /*!< 4 forms: 90 | 91 | - is 1 or 11, 92 | 93 | - is 2 or 12, 94 | 95 | - is 3-10 or 13-19 96 | 97 | - everything else */ 98 | 99 | JJPluralRuleRomanian = 5, /*!< 3 forms: 100 | 101 | - is 1, 102 | 103 | - is 0 or ends in 01-19, excluding 1 104 | 105 | - everything else */ 106 | 107 | JJPluralRuleLithuanian = 6, /*!< 3 forms: 108 | 109 | - ends in 1, excluding 11 110 | 111 | - ends in 0, or ends in 11-19 112 | 113 | - everything else */ 114 | 115 | JJPluralRuleRussian = 7, /*!< 3 forms: 116 | 117 | - ends in 1, excluding 11 118 | 119 | - ends in 2-4, excluding 12-14 120 | 121 | - everything else */ 122 | 123 | JJPluralRuleRussianExtended = 107, /*!< 4 forms. Extended rule #7 from 124 | 3 to 4 forms by adding 'is 1'. 125 | 126 | - is 1 127 | 128 | - ends in 1, excluding 1 and 11 129 | 130 | - ends in 2-4, excluding 12-14 131 | 132 | - everything else */ 133 | 134 | JJPluralRuleCzech = 8, /*!< 3 forms: 135 | 136 | - is 1 137 | 138 | - is 2-4 139 | 140 | - everything else, 141 | 142 | e.g. Czech, Slovak */ 143 | 144 | JJPluralRulePolish = 9, /*!< 3 forms: 145 | 146 | - is 1 147 | 148 | - ends in 2-4, excluding 12-14 149 | 150 | - everything else */ 151 | 152 | JJPluralRuleSlovenian = 10, /*!< 4 forms: 153 | 154 | - ends in 01 155 | 156 | - ends in 02 157 | 158 | - ends in 03-04 159 | 160 | - everything else */ 161 | 162 | JJPluralRuleIrishGaelic = 11, /*!< 5 forms: 163 | 164 | - is 1 165 | 166 | - is 2 167 | 168 | - is 3-6 169 | 170 | - is 7-10 171 | 172 | - everything else */ 173 | 174 | JJPluralRuleArabic = 12, /*!< 6 forms: 175 | 176 | - is 1 177 | 178 | - is 2 179 | 180 | - ends in 03-10 181 | 182 | - everything else but 0, ends in 00-02, 183 | excluding 0-2 184 | 185 | - ends in 00-02, excluding 0-2 186 | 187 | - is 0 */ 188 | 189 | JJPluralRuleMaltese = 13, /*!< 4 forms: 190 | 191 | - is 1 192 | 193 | - is 0 or ends in 01-10, excluding 1 194 | 195 | - ends in 11-19 196 | 197 | - everything else */ 198 | 199 | JJPluralRuleMacedonian = 14, /*!< 3 forms: 200 | 201 | - ends in 1 202 | 203 | - ends in 2 204 | 205 | - everything else */ 206 | 207 | JJPluralRuleIcelandic = 15, /*!< 2 forms: 208 | 209 | - ends in 1, excluding 11 210 | 211 | - everything else */ 212 | 213 | JJPluralRuleBreton = 16, /*!< 6 forms: 214 | 215 | - is 1 216 | 217 | - ends in 1, excluding 1, 11, 71, 91 218 | 219 | - ends in 2, excluding 12, 72, 92 220 | 221 | - ends in 3, 4 or 9 excluding 13, 14, 222 | 19, 73, 74, 79, 93, 94, 99 223 | 224 | - ends in 1000000 225 | 226 | - everything else */ 227 | 228 | JJPluralRuleNotARule = 999999 /*!< Not a plural rule */ 229 | }; 230 | 231 | @interface JJPluralForm : NSObject 232 | 233 | /** 234 | * Sets the default plural rule to use if none is specified. This is required 235 | * if you're using the method \c +pluralStringForNumber:withPluralForms:. 236 | */ 237 | + (void)setPluralRule:(JJPluralRule)rule; 238 | 239 | /** 240 | * Returns the default plural rule to use if none is specified. 241 | * Default: \c JJPluralRuleNotARule 242 | */ 243 | + (JJPluralRule)pluralRule; 244 | 245 | /** 246 | * Sets the default separator for plural forms if none is specified. By default 247 | * plural forms are separated by semicolons (;), so a plural form string for the 248 | * word 'day' in English would be 'day;days'. Default: \c ; 249 | */ 250 | + (void)setPluralFormsSeparator:(NSString *)separator; 251 | 252 | /** 253 | * Returns the default separator for plural forms. Default: \c ; 254 | */ 255 | + (NSString *)pluralFormsSeparator; 256 | 257 | /** 258 | * Sets whether numbers should be localized with the \c +defaultNumberFormatter 259 | * before being returned in methods that don't specify them. This primarily 260 | * affects locales using a different set of numeral symbols such as Arabic. 261 | * Default: \c YES. 262 | */ 263 | + (void)setShouldLocalizeNumeral:(BOOL)localizeNumeral; 264 | 265 | /** 266 | * Returns whether numbers should be localized with the \c +defaultNumberFormatter 267 | * before being returned in methods that don't specify them. Default: \c YES. 268 | */ 269 | + (BOOL)shouldLocalizeNumeral; 270 | 271 | /** 272 | * Sets a custom number formatter to format numbers returned by the various 273 | * \c pluralStringForNumber: methods. 274 | * Default: An \c NSNumberFormatter initialized to current locale with 275 | * \c NSNumberFormatterNoStyle. 276 | */ 277 | + (void)setDefaultNumberFormatter:(NSNumberFormatter *)formatter; 278 | 279 | /** 280 | * Returns the default number formatter. Default: An \c NSNumberFormatter 281 | * initialized to current locale with \c NSNumberFormatterNoStyle. 282 | */ 283 | + (NSNumberFormatter *)defaultNumberFormatter; 284 | 285 | /** 286 | * Returns a string containing \c number and the correct plural form of a word 287 | * when qualified by the number. For example, returns '1 day' when \c number is 288 | * 1, but '2 days' when \c number is 2 with the \c pluralForms "%@ day;%@ days" 289 | * when using the \c JJPluralRuleEnglish as the plural rule. Before using this 290 | * method, you must first configure the default plural rule with 291 | * \c +setPluralRule: 292 | * 293 | * @param number The number qualifying the word that we're interested in, e.g. 294 | * the number '2' in '2 days'. 295 | * @param pluralForms A semi-colon separated list of different forms of the 296 | * word. The order of the words must follow the order of the rule specified 297 | * in \c JJPluralRule. For example, English uses \c JJPluralRuleEnglish (rule 298 | * #1), which takes 2 forms (is 1, everything else). Localizing the phrase 299 | * 'N days' would require a string '%@ day;%@ days', where the first form 300 | * ('%@ day') would be used when \c number is '1', and the second form 301 | * ('%@ days') for every other number. 302 | * 303 | * @returns The \c number and the correct plural form of a word when qualified 304 | * by \c number (e.g. '2 days'). 305 | */ 306 | + (NSString *)pluralStringForNumber:(NSUInteger)number 307 | withPluralForms:(NSString *)pluralForms; 308 | 309 | /** 310 | * Returns a string containing \c number and the correct plural form of a word 311 | * when qualified by the number. For example, returns '1 day' when \c number is 312 | * 1, but '2 days' when \c number is 2 for \c pluralRule of 313 | * \c JJPluralRuleEnglish, and the \c pluralForms "%@ day;%@ days". 314 | * 315 | * @param number The number qualifying the word that we're interested in, e.g. 316 | * the number '2' in '2 days'. 317 | * @param pluralForms A semi-colon separated list of different forms of the 318 | * word. The order of the words must follow the order of the rule specified 319 | * in \c JJPluralRule. For example, English uses \c JJPluralRuleEnglish (rule 320 | * #1), which takes 2 forms (is 1, everything else). Localizing the phrase 321 | * 'N days' would require a string '%@ day;%@ days', where the first form 322 | * ('%@ day') would be used when \c number is '1', and the second form 323 | * ('%@ days') for every other number. 324 | * @param pluralRule Specifies the grammatical rule that governs which form of 325 | * word should be used for the given \c number. See \c JJPluralRule more 326 | * supported rules, or read more at Mozilla's website: 327 | * https://developer.mozilla.org/en/Localization_and_Plurals 328 | * @param localizeNumeral If \c YES, \c number should be localized with 329 | * \c NSNumberFormatter before being returned in the string. This primarily 330 | * affects locales using a different set of numeral symbols such as Arabic. 331 | * 332 | * @returns The \c number and the correct plural form of a word when qualified 333 | * by \c number (e.g. '2 days'). 334 | */ 335 | + (NSString *)pluralStringForNumber:(NSUInteger)number 336 | withPluralForms:(NSString *)pluralForms 337 | usingPluralRule:(JJPluralRule)pluralRule 338 | localizeNumeral:(BOOL)localizeNumeral; 339 | 340 | /** 341 | * Returns a string containing \c number and the correct plural form of a word 342 | * when qualified by the number. For example, returns '1 day' when \c number is 343 | * 1, but '2 days' when \c number is 2 for \c pluralRule of \c JJPluralRuleEnglish, 344 | * and the \c pluralForms "%@ day;%@ days". This method allows (and requires) 345 | * you to specify a custom \c separator for the forms in \c pluralForms. 346 | * 347 | * @param number The number qualifying the word that we're interested in, e.g. 348 | * the number '2' in '2 days'. 349 | * @param pluralForms A list of different forms of the word separated by 350 | * \c separator. The order of the words must follow the order of the rule 351 | * specified in \c JJPluralRule. For example, English uses 352 | * \c JJPluralRuleEnglish (rule #1), which takes 2 forms (is 1, everything 353 | * else). If \c separator is '|', localizing the phrase 'N days' would require a 354 | * string '%@ day|%@ days', where the first form ('%@ day') would be used when 355 | * \c number is '1', and the second form ('%@ days') for every other number. 356 | * @param separator Specifies how the different plural forms of a word (eg. day, 357 | * days) in the \c pluralForms are separated 358 | * @param pluralRule Specifies the grammatical rule that governs which form of 359 | * word should be used for the given \c number. See \c JJPluralRule more 360 | * supported rules, or read more at Mozilla's website: 361 | * https://developer.mozilla.org/en/Localization_and_Plurals 362 | * @param localizeNumeral If \c YES, \c number should be localized with 363 | * \c NSNumberFormatter before being returned in the string. This primarily 364 | * affects locales using a different set of numeral symbols such as Arabic. 365 | * 366 | * @returns The \c number and the correct plural form of a word when qualified 367 | * by \c number (e.g. '2 days'). 368 | */ 369 | + (NSString *)pluralStringForNumber:(NSUInteger)number 370 | withPluralForms:(NSString *)pluralForms 371 | separatedBy:(NSString *)separator 372 | usingPluralRule:(JJPluralRule)pluralRule 373 | localizeNumeral:(BOOL)localizeNumeral; 374 | 375 | /** 376 | * Returns a string containing \c number and the correct plural form of a word 377 | * when qualified by the number. For example, returns '1 day' when \c number is 378 | * 1, but '2 days' when \c number is 2 for \c pluralRule of \c JJPluralRuleEnglish, 379 | * and the \c pluralForms "%@ day;%@ days". This method allows (and requires) 380 | * you to specify a custom \c separator for the forms in \c pluralForms and 381 | * a custom number formatter to format your number. 382 | * 383 | * @param number The number qualifying the word that we're interested in, e.g. 384 | * the number '2' in '2 days'. 385 | * @param pluralForms A list of different forms of the word separated by 386 | * \c separator. The order of the words must follow the order of the rule 387 | * specified in \c JJPluralRule. For example, English uses 388 | * \c JJPluralRuleEnglish (rule #1), which takes 2 forms (is 1, everything 389 | * else). If \c separator is '|', localizing the phrase 'N days' would require a 390 | * string '%@ day|%@ days', where the first form ('%@ day') would be used when 391 | * \c number is '1', and the second form ('%@ days') for every other number. 392 | * @param separator Specifies how the different plural forms of a word (eg. day, 393 | * days) in the \c pluralForms are separated 394 | * @param pluralRule Specifies the grammatical rule that governs which form of 395 | * word should be used for the given \c number. See \c JJPluralRule more 396 | * supported rules, or read more at Mozilla's website: 397 | * https://developer.mozilla.org/en/Localization_and_Plurals 398 | * @param numberFormatter If provided, \c number would be localized with 399 | * the formatter before being returned in the string. 400 | * 401 | * @returns The \c number and the correct plural form of a word when qualified 402 | * by \c number (e.g. '2 days'). 403 | */ 404 | 405 | + (NSString *)pluralStringForNumber:(NSUInteger)number 406 | withPluralForms:(NSString *)pluralForms 407 | separatedBy:(NSString *)separator 408 | usingPluralRule:(JJPluralRule)pluralRule 409 | numberFormatter:(NSNumberFormatter *)numberFormatter; 410 | 411 | @end 412 | -------------------------------------------------------------------------------- /JJPluralForm/JJPluralForm.m: -------------------------------------------------------------------------------- 1 | // 2 | // JJPluralForm.h 3 | // JJPluralForm 2.0 4 | // 5 | // Created by Lin Junjie (Clean Shaven Apps Pte. Ltd.) on 22/2/14. 6 | // Copyright (c) 2014 Lin Junjie. All rights reserved. 7 | // 8 | // This Source Code Form is subject to the terms of the Mozilla Public 9 | // License, v. 2.0. If a copy of the MPL was not distributed with this 10 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 11 | 12 | #import "JJPluralForm.h" 13 | 14 | static JJPluralRule _defaultPluralRule = JJPluralRuleNotARule; 15 | static NSString *_defaultSeparator = @";"; 16 | static BOOL _defaultLocalizeNumber = YES; 17 | static NSNumberFormatter *_defaultNumberFormatter = nil; 18 | 19 | @interface JJPluralForm () 20 | 21 | @end 22 | 23 | @implementation JJPluralForm 24 | 25 | + (void)initialize 26 | { 27 | _defaultNumberFormatter = [NSNumberFormatter new]; 28 | [_defaultNumberFormatter setNumberStyle:NSNumberFormatterNoStyle]; 29 | } 30 | 31 | + (void)setPluralRule:(JJPluralRule)rule 32 | { 33 | _defaultPluralRule = rule; 34 | } 35 | 36 | + (JJPluralRule)pluralRule 37 | { 38 | return _defaultPluralRule; 39 | } 40 | 41 | + (void)setPluralFormsSeparator:(NSString *)separator 42 | { 43 | _defaultSeparator = separator; 44 | } 45 | 46 | + (NSString *)pluralFormsSeparator 47 | { 48 | return _defaultSeparator; 49 | } 50 | 51 | + (void)setShouldLocalizeNumeral:(BOOL)localizeNumeral 52 | { 53 | _defaultLocalizeNumber = localizeNumeral; 54 | } 55 | 56 | + (BOOL)shouldLocalizeNumeral 57 | { 58 | return _defaultLocalizeNumber; 59 | } 60 | 61 | + (void)setDefaultNumberFormatter:(NSNumberFormatter *)formatter 62 | { 63 | _defaultNumberFormatter = [formatter copy]; 64 | } 65 | 66 | + (NSNumberFormatter *)defaultNumberFormatter 67 | { 68 | return _defaultNumberFormatter; 69 | } 70 | 71 | #pragma mark - Methods to Obtain Plural Form 72 | 73 | + (NSString *)pluralStringForNumber:(NSUInteger)number 74 | withPluralForms:(NSString *)pluralForms 75 | { 76 | return [self pluralStringForNumber:number 77 | withPluralForms:pluralForms 78 | separatedBy:_defaultSeparator 79 | usingPluralRule:_defaultPluralRule 80 | localizeNumeral:_defaultLocalizeNumber]; 81 | } 82 | 83 | + (NSString *)pluralStringForNumber:(NSUInteger)number 84 | withPluralForms:(NSString *)pluralForms 85 | usingPluralRule:(JJPluralRule)pluralRule 86 | localizeNumeral:(BOOL)localizeNumeral 87 | { 88 | return [self pluralStringForNumber:number 89 | withPluralForms:pluralForms 90 | separatedBy:_defaultSeparator 91 | usingPluralRule:pluralRule 92 | localizeNumeral:localizeNumeral]; 93 | } 94 | 95 | + (NSString *)pluralStringForNumber:(NSUInteger)number 96 | withPluralForms:(NSString *)pluralForms 97 | separatedBy:(NSString *)separator 98 | usingPluralRule:(JJPluralRule)pluralRule 99 | localizeNumeral:(BOOL)localizeNumeral 100 | { 101 | NSNumberFormatter *formatter = 102 | localizeNumeral ? 103 | _defaultNumberFormatter : 104 | nil; 105 | 106 | return [self pluralStringForNumber:number 107 | withPluralForms:pluralForms 108 | separatedBy:separator 109 | usingPluralRule:pluralRule 110 | numberFormatter:formatter]; 111 | } 112 | 113 | + (NSString *)pluralStringForNumber:(NSUInteger)number 114 | withPluralForms:(NSString *)pluralForms 115 | separatedBy:(NSString *)separator 116 | usingPluralRule:(JJPluralRule)pluralRule 117 | numberFormatter:(NSNumberFormatter *)numberFormatter 118 | { 119 | NSUInteger expectedNumberOfForms = NSNotFound; 120 | NSUInteger idx = 121 | [[self class] jj_indexOfPluralFormForNumber:number 122 | pluralRule:pluralRule 123 | getNumberOfForms:&expectedNumberOfForms]; 124 | 125 | 126 | if (expectedNumberOfForms == NSNotFound) 127 | { 128 | NSAssert(0, @"Plural rule %lu out of bounds", (unsigned long)pluralRule); 129 | return @"ERR"; 130 | } 131 | 132 | NSArray* pluralFormsArray = [pluralForms componentsSeparatedByString:separator]; 133 | NSUInteger numberOfForms = [pluralFormsArray count]; 134 | 135 | if (numberOfForms != expectedNumberOfForms) 136 | { 137 | NSAssert(0, @"Plural rule %lu requires %lu form(s), but %lu form(s) is/are found in '%@'", (unsigned long)pluralRule, (unsigned long)expectedNumberOfForms, (unsigned long)numberOfForms, pluralForms); 138 | return @"ERR"; 139 | } 140 | 141 | else if (idx >= numberOfForms) 142 | { 143 | NSAssert(idx < numberOfForms, @"Plural form %lu exceeds number of plural form(s) (%lu) in string (%@) (using plural rule %lu)", (unsigned long)idx, (unsigned long)numberOfForms, pluralForms, (unsigned long)pluralRule); 144 | return @"ERR"; 145 | } 146 | 147 | NSString* numberString = nil; 148 | NSString* pluralForm = nil; 149 | 150 | if (numberFormatter) 151 | { 152 | numberString = [numberFormatter stringFromNumber:@(number)]; 153 | } 154 | else 155 | { 156 | numberString = [@(number) stringValue]; 157 | } 158 | 159 | if (!pluralForm) 160 | { 161 | pluralForm = pluralFormsArray[idx]; 162 | } 163 | 164 | return [NSString stringWithFormat:pluralForm, numberString]; 165 | } 166 | 167 | #pragma mark - Pluralisation Helper Methods 168 | 169 | /** 170 | * Returns the correct plural form for number \c n based on plural rule \c rule 171 | * 172 | * @param n The number qualifying the word. 173 | * @param pluralRule The plural rule. See \c JJPluralRule for options. 174 | * @param numberOfFormsOut Upon return, contains the number of forms expected 175 | * for pluralRule \c rule. Can be \c NULL. 176 | * 177 | * @returns The correct plural form to use for a word for number \c n. Returns 178 | * \c NSNotFound if rule is invalid. 179 | */ 180 | + (NSUInteger)jj_indexOfPluralFormForNumber:(NSUInteger)n 181 | pluralRule:(JJPluralRule)pluralRule 182 | getNumberOfForms:(NSUInteger *)numberOfFormsOut 183 | { 184 | NSUInteger index = NSNotFound; 185 | NSUInteger numberOfForms = NSNotFound; 186 | 187 | switch (pluralRule) 188 | { 189 | case JJPluralRuleAsian: 190 | { 191 | // Rule #0, 1 form 192 | // Mostly Asian, e.g. Chinese, Japanese, Korean, Vietnamese, Thai, Lao 193 | // 1) everything 194 | index = 0; 195 | numberOfForms = 1; 196 | break; 197 | } 198 | 199 | case JJPluralRuleAsianExtended: 200 | { 201 | // Rule #100, extended from 1 to 3 forms 202 | // Mostly Asian, e.g. Chinese, Japanese, Korean, Vietnamese, Thai, Lao. Swedish 203 | // 1) is 1 204 | // 2) is 2 205 | // 3) everything else 206 | index = n==1 ? 0 : (n==2 ? 1 : 2); 207 | numberOfForms = 3; 208 | break; 209 | } 210 | 211 | case JJPluralRuleEnglish: 212 | { 213 | // Rule #1, 2 forms 214 | // English, German, Dutch, Norwegian, Swedish, Italian, Portuguese, Spanish, etc. 215 | // 1) is 1 216 | // 2) everything else 217 | index = n != 1 ? 1 : 0; 218 | numberOfForms = 2; 219 | break; 220 | } 221 | 222 | case JJPluralRuleFrench: 223 | { 224 | // Rule #2, 2 forms 225 | // French, Brazilian Portuguese 226 | // 1) is 0 or 1 227 | // 2) everything else 228 | index = n > 1 ? 1 : 0; 229 | numberOfForms = 2; 230 | break; 231 | } 232 | 233 | case JJPluralRuleFrenchExtended: 234 | { 235 | // Rule #102, extended from 2 to 3 forms by separating 'is 0 or 1' 236 | // French, Brazilian Portuguese 237 | // 1) is 0 238 | // 2) is 1 239 | // 3) everything else 240 | index = n==0 ? 0 : (n==1 ? 1 : 2); 241 | numberOfForms = 3; 242 | break; 243 | } 244 | 245 | case JJPluralRuleLatvian: 246 | { 247 | // Rule #3, 3 forms 248 | // Latvian 249 | // 1) is 0 250 | // 2) ends in 1, excluding 11 251 | // 3) everything else 252 | index = (n % 10==1 && n % 100 != 11) ? 1 : (n != 0 ? 2 : 0); 253 | numberOfForms = 3; 254 | break; 255 | } 256 | 257 | case JJPluralRuleScottishGaelic: 258 | { 259 | // Rule #4, 4 forms 260 | // Scottish Gaelic 261 | // 1) is 1 or 11 262 | // 2) is 2 or 12 263 | // 3) is 3-10 or 13-19 264 | // 4) everything else 265 | index = (n==1 || n==11) ? 0 : ( 266 | n==2 || n==12 ? 1 : ( 267 | n>0 && n<20 ? 2 : 3 268 | ) 269 | ); 270 | numberOfForms = 4; 271 | break; 272 | } 273 | 274 | case JJPluralRuleRomanian: 275 | { 276 | // Rule #5, 3 forms 277 | // Romanian 278 | // 1) is 1, 279 | // 2) is 0 or ends in 01-19, excluding 1 280 | // 3) everything else 281 | index = n==1 ? 0 : ( 282 | (n==0||(n%100>0&&n%100<20)) ? 1 : 2 283 | ); 284 | numberOfForms = 3; 285 | break; 286 | } 287 | 288 | case JJPluralRuleLithuanian: 289 | { 290 | // Rule #6, 3 forms 291 | // Lithuanian 292 | // 1) ends in 1, excluding 11 293 | // 2) ends in 0, or ends in 11-19 294 | // 3) everything else 295 | index = n%10==1&&n%100!=11 ? 0 : ( 296 | n%10>=2&&(n%100<10||n%100>=20) ? 2 : 1 297 | ); 298 | numberOfForms = 3; 299 | break; 300 | } 301 | 302 | case JJPluralRuleRussian: 303 | { 304 | // Rule #7, 3 forms 305 | // Russian 306 | // 1) ends in 1, excluding 11 307 | // 2) ends in 2-4, excluding 12-14 308 | // 3) everything else 309 | index = n%10==1&&n%100!=11 ? 0 : ( 310 | n%10>=2&&n%10<=4&&(n%100<10||n%100>=20) ? 1 : 2 311 | ); 312 | numberOfForms = 3; 313 | break; 314 | } 315 | 316 | case JJPluralRuleRussianExtended: 317 | { 318 | // Rule #107, extended from 3 to 4 forms by adding 'is 1' 319 | // Russian 320 | // 1) is 1 321 | // 2) ends in 1, excluding 1 and 11 322 | // 3) ends in 2-4, excluding 12-14 323 | // 4) everything else 324 | index = n==1 ? 0 : ( 325 | n%10==1&&n%100!=11 ? 1 : ( 326 | n%10>=2&&n%10<=4&&(n%100<10||n%100>=20) ? 2 : 3 327 | ) 328 | ); 329 | numberOfForms = 4; 330 | break; 331 | } 332 | 333 | case JJPluralRuleCzech: 334 | { 335 | // Rule #8, 3 forms 336 | // Czech, Slovak 337 | // 1) is 1 338 | // 2) is 2-4 339 | // 3) everything else 340 | index = n==1 ? 0 : (n>=2&&n<=4 ? 1 : 2); 341 | numberOfForms = 3; 342 | break; 343 | } 344 | 345 | case JJPluralRulePolish: 346 | { 347 | // Rule #9, 3 forms 348 | // Polish 349 | // 1) is 1 350 | // 2) ends in 2-4, excluding 12-14 351 | // 3) everything else 352 | index = n==1 ? 0 : ( 353 | n%10>=2&&n%10<=4&&(n%100<10||n%100>=20) ? 1 : 2 354 | ); 355 | numberOfForms = 3; 356 | break; 357 | } 358 | 359 | case JJPluralRuleSlovenian: 360 | { 361 | // Rule #10, 4 forms 362 | // Slovenian, Sorbian 363 | // 1) ends in 01 364 | // 2) ends in 02 365 | // 3) ends in 03-04 366 | // 4) everything else 367 | index = n%100==1 ? 0 : ( 368 | n%100==2 ? 1 : ( 369 | n%100==3||n%100==4 ? 2 : 3 370 | ) 371 | ); 372 | numberOfForms = 4; 373 | break; 374 | } 375 | 376 | case JJPluralRuleIrishGaelic: 377 | { 378 | // Rule #11, 5 forms 379 | // Irish Gaelic 380 | // 1) is 1 381 | // 2) is 2 382 | // 3) is 3-6 383 | // 4) is 7-10 384 | // 5) everything else 385 | index = n==1 ? 0 : ( 386 | n==2 ? 1 : ( 387 | n>=3&&n<=6 ? 2 : ( 388 | n>=7&&n<=10 ? 3 : 4 389 | ) 390 | ) 391 | ); 392 | numberOfForms = 5; 393 | break; 394 | } 395 | 396 | case JJPluralRuleArabic: 397 | { 398 | // Rule #12, 6 forms 399 | // Arabic 400 | // 1) is 1 401 | // 2) is 2 402 | // 3) ends in 03-10 403 | // 4) everything else but 0, ends in 00-02, excluding 0-2 404 | // 5) ends in 00-02, excluding 0-2 405 | // 6) is 0 406 | index = n==0 ? 5 : ( 407 | n==1 ? 0 : ( 408 | n==2 ? 1 : ( 409 | n%100>=3&&n%100<=10 ? 2 : ( 410 | n%100>=11&&n%100<=99 ? 3 : 4 411 | ) 412 | ) 413 | ) 414 | ); 415 | numberOfForms = 6; 416 | break; 417 | } 418 | 419 | case JJPluralRuleMaltese: 420 | { 421 | // Rule #13, 4 forms 422 | // Maltese 423 | // 1) is 1 424 | // 2) is 0 or ends in 01-10, excluding 1 425 | // 3) ends in 11-19 426 | // 4) everything else 427 | index = n==1 ? 0 : ( 428 | n==0||(n%100>0&&n%100<=10) ? 1 : ( 429 | n%100>10&&n%100<20 ? 2 : 3 430 | ) 431 | ); 432 | numberOfForms = 4; 433 | break; 434 | } 435 | 436 | case JJPluralRuleMacedonian: 437 | { 438 | // Rule #14, 3 forms 439 | // Macedonian 440 | // 1) ends in 1 441 | // 2) ends in 2 442 | // 3) everything else 443 | index = n%10==1 ? 0 : ( 444 | n%10==2 ? 1 : 2 445 | ); 446 | numberOfForms = 3; 447 | break; 448 | } 449 | 450 | case JJPluralRuleIcelandic: 451 | { 452 | // Rule #15, 2 forms 453 | // Icelandic 454 | // 1) ends in 1, excluding 11 455 | // 2) everything else 456 | index = n%10==1&&n%100!=11 ? 0 : 1; 457 | numberOfForms = 2; 458 | break; 459 | } 460 | 461 | case JJPluralRuleBreton: 462 | { 463 | // Rule #16, 6 forms 464 | // Breton 465 | // 1) is 1 466 | // 2) ends in 1, excluding 1, 11, 71, 91 467 | // 3) ends in 2, excluding 12, 72, 92 468 | // 4) ends in 3, 4 or 9 excluding 13, 14, 19, 73, 74, 79, 93, 94, 99 469 | // 5) ends in 1000000 470 | // 6) everything else 471 | index = n%10==1&&n%100!=11&&n%100!=71&&n%100!=91?0:n%10==2&&n%100!=12&&n%100!=72&&n%100!=92?1:(n%10==3||n%10==4||n%10==9)&&n%100!=13&&n%100!=14&&n%100!=19&&n%100!=73&&n%100!=74&&n%100!=79&&n%100!=93&&n%100!=94&&n%100!=99?2:n%1000000==0&&n!=0?3:4; 472 | numberOfForms = 6; 473 | break; 474 | } 475 | 476 | case JJPluralRuleNotARule: 477 | { 478 | NSLog(@"A plural rule has not been specified."); 479 | break; 480 | } 481 | 482 | default: 483 | { 484 | NSLog(@"Invalid plural rule (%ld) specified.", (long)pluralRule); 485 | break; 486 | } 487 | } 488 | 489 | if (numberOfFormsOut != NULL) 490 | { 491 | *numberOfFormsOut = numberOfForms; 492 | } 493 | 494 | return index; 495 | } 496 | 497 | @end 498 | -------------------------------------------------------------------------------- /JJPluralFormExample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | B321A96C18B8EF29002B3EAD /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = B321A96B18B8EF29002B3EAD /* Default-568h@2x.png */; }; 11 | B35C49D915B69B0000ED5A08 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B35C49D815B69B0000ED5A08 /* UIKit.framework */; }; 12 | B35C49DB15B69B0000ED5A08 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B35C49DA15B69B0000ED5A08 /* Foundation.framework */; }; 13 | B35C49DD15B69B0000ED5A08 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B35C49DC15B69B0000ED5A08 /* CoreGraphics.framework */; }; 14 | B35C49E315B69B0000ED5A08 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = B35C49E115B69B0000ED5A08 /* InfoPlist.strings */; }; 15 | B35C49E515B69B0000ED5A08 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = B35C49E415B69B0000ED5A08 /* main.m */; }; 16 | B35C49E915B69B0000ED5A08 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = B35C49E815B69B0000ED5A08 /* AppDelegate.m */; }; 17 | B35C49F715B69B1C00ED5A08 /* JJPluralForm.m in Sources */ = {isa = PBXBuildFile; fileRef = B35C49F615B69B1C00ED5A08 /* JJPluralForm.m */; }; 18 | B35C49FB15B6B97600ED5A08 /* LocalizationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B35C49FA15B6B97600ED5A08 /* LocalizationViewController.m */; }; 19 | B35C49FE15B6BC5E00ED5A08 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = B35C4A0015B6BC5E00ED5A08 /* Localizable.strings */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXFileReference section */ 23 | B321A96B18B8EF29002B3EAD /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = ""; }; 24 | B35C49D415B69B0000ED5A08 /* JJPluralFormExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = JJPluralFormExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 25 | B35C49D815B69B0000ED5A08 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 26 | B35C49DA15B69B0000ED5A08 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 27 | B35C49DC15B69B0000ED5A08 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 28 | B35C49E015B69B0000ED5A08 /* JJPluralFormExample-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "JJPluralFormExample-Info.plist"; sourceTree = ""; }; 29 | B35C49E215B69B0000ED5A08 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 30 | B35C49E415B69B0000ED5A08 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 31 | B35C49E615B69B0000ED5A08 /* JJPluralFormExample-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "JJPluralFormExample-Prefix.pch"; sourceTree = ""; }; 32 | B35C49E715B69B0000ED5A08 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 33 | B35C49E815B69B0000ED5A08 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 34 | B35C49F515B69B1C00ED5A08 /* JJPluralForm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = JJPluralForm.h; path = JJPluralForm/JJPluralForm.h; sourceTree = ""; }; 35 | B35C49F615B69B1C00ED5A08 /* JJPluralForm.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = JJPluralForm.m; path = JJPluralForm/JJPluralForm.m; sourceTree = ""; }; 36 | B35C49F915B6B97600ED5A08 /* LocalizationViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LocalizationViewController.h; sourceTree = ""; }; 37 | B35C49FA15B6B97600ED5A08 /* LocalizationViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LocalizationViewController.m; sourceTree = ""; }; 38 | B35C49FF15B6BC5E00ED5A08 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; 39 | B35C4A9B15B6C8F700ED5A08 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; 40 | B35C4AA315B6CBAD00ED5A08 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; 41 | B35C4AA415B6CDF200ED5A08 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Localizable.strings; sourceTree = ""; }; 42 | /* End PBXFileReference section */ 43 | 44 | /* Begin PBXFrameworksBuildPhase section */ 45 | B35C49D115B69B0000ED5A08 /* Frameworks */ = { 46 | isa = PBXFrameworksBuildPhase; 47 | buildActionMask = 2147483647; 48 | files = ( 49 | B35C49D915B69B0000ED5A08 /* UIKit.framework in Frameworks */, 50 | B35C49DB15B69B0000ED5A08 /* Foundation.framework in Frameworks */, 51 | B35C49DD15B69B0000ED5A08 /* CoreGraphics.framework in Frameworks */, 52 | ); 53 | runOnlyForDeploymentPostprocessing = 0; 54 | }; 55 | /* End PBXFrameworksBuildPhase section */ 56 | 57 | /* Begin PBXGroup section */ 58 | B35C49C915B69B0000ED5A08 = { 59 | isa = PBXGroup; 60 | children = ( 61 | B321A96B18B8EF29002B3EAD /* Default-568h@2x.png */, 62 | B35C49F815B69B2200ED5A08 /* JJPluralForm */, 63 | B35C49DE15B69B0000ED5A08 /* JJPluralFormExample */, 64 | B35C49D715B69B0000ED5A08 /* Frameworks */, 65 | B35C49D515B69B0000ED5A08 /* Products */, 66 | ); 67 | sourceTree = ""; 68 | }; 69 | B35C49D515B69B0000ED5A08 /* Products */ = { 70 | isa = PBXGroup; 71 | children = ( 72 | B35C49D415B69B0000ED5A08 /* JJPluralFormExample.app */, 73 | ); 74 | name = Products; 75 | sourceTree = ""; 76 | }; 77 | B35C49D715B69B0000ED5A08 /* Frameworks */ = { 78 | isa = PBXGroup; 79 | children = ( 80 | B35C49D815B69B0000ED5A08 /* UIKit.framework */, 81 | B35C49DA15B69B0000ED5A08 /* Foundation.framework */, 82 | B35C49DC15B69B0000ED5A08 /* CoreGraphics.framework */, 83 | ); 84 | name = Frameworks; 85 | sourceTree = ""; 86 | }; 87 | B35C49DE15B69B0000ED5A08 /* JJPluralFormExample */ = { 88 | isa = PBXGroup; 89 | children = ( 90 | B35C49E715B69B0000ED5A08 /* AppDelegate.h */, 91 | B35C49E815B69B0000ED5A08 /* AppDelegate.m */, 92 | B35C49F915B6B97600ED5A08 /* LocalizationViewController.h */, 93 | B35C49FA15B6B97600ED5A08 /* LocalizationViewController.m */, 94 | B35C4A0015B6BC5E00ED5A08 /* Localizable.strings */, 95 | B35C49DF15B69B0000ED5A08 /* Supporting Files */, 96 | ); 97 | path = JJPluralFormExample; 98 | sourceTree = ""; 99 | }; 100 | B35C49DF15B69B0000ED5A08 /* Supporting Files */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | B35C49E015B69B0000ED5A08 /* JJPluralFormExample-Info.plist */, 104 | B35C49E115B69B0000ED5A08 /* InfoPlist.strings */, 105 | B35C49E415B69B0000ED5A08 /* main.m */, 106 | B35C49E615B69B0000ED5A08 /* JJPluralFormExample-Prefix.pch */, 107 | ); 108 | name = "Supporting Files"; 109 | sourceTree = ""; 110 | }; 111 | B35C49F815B69B2200ED5A08 /* JJPluralForm */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | B35C49F515B69B1C00ED5A08 /* JJPluralForm.h */, 115 | B35C49F615B69B1C00ED5A08 /* JJPluralForm.m */, 116 | ); 117 | name = JJPluralForm; 118 | sourceTree = ""; 119 | }; 120 | /* End PBXGroup section */ 121 | 122 | /* Begin PBXNativeTarget section */ 123 | B35C49D315B69B0000ED5A08 /* JJPluralFormExample */ = { 124 | isa = PBXNativeTarget; 125 | buildConfigurationList = B35C49F215B69B0000ED5A08 /* Build configuration list for PBXNativeTarget "JJPluralFormExample" */; 126 | buildPhases = ( 127 | B35C49D015B69B0000ED5A08 /* Sources */, 128 | B35C49D115B69B0000ED5A08 /* Frameworks */, 129 | B35C49D215B69B0000ED5A08 /* Resources */, 130 | ); 131 | buildRules = ( 132 | ); 133 | dependencies = ( 134 | ); 135 | name = JJPluralFormExample; 136 | productName = JJPluralFormExample; 137 | productReference = B35C49D415B69B0000ED5A08 /* JJPluralFormExample.app */; 138 | productType = "com.apple.product-type.application"; 139 | }; 140 | /* End PBXNativeTarget section */ 141 | 142 | /* Begin PBXProject section */ 143 | B35C49CB15B69B0000ED5A08 /* Project object */ = { 144 | isa = PBXProject; 145 | attributes = { 146 | LastUpgradeCheck = 0510; 147 | ORGANIZATIONNAME = "Lin Junjie"; 148 | }; 149 | buildConfigurationList = B35C49CE15B69B0000ED5A08 /* Build configuration list for PBXProject "JJPluralFormExample" */; 150 | compatibilityVersion = "Xcode 3.2"; 151 | developmentRegion = English; 152 | hasScannedForEncodings = 0; 153 | knownRegions = ( 154 | en, 155 | "zh-Hans", 156 | ru, 157 | ar, 158 | ); 159 | mainGroup = B35C49C915B69B0000ED5A08; 160 | productRefGroup = B35C49D515B69B0000ED5A08 /* Products */; 161 | projectDirPath = ""; 162 | projectRoot = ""; 163 | targets = ( 164 | B35C49D315B69B0000ED5A08 /* JJPluralFormExample */, 165 | ); 166 | }; 167 | /* End PBXProject section */ 168 | 169 | /* Begin PBXResourcesBuildPhase section */ 170 | B35C49D215B69B0000ED5A08 /* Resources */ = { 171 | isa = PBXResourcesBuildPhase; 172 | buildActionMask = 2147483647; 173 | files = ( 174 | B35C49E315B69B0000ED5A08 /* InfoPlist.strings in Resources */, 175 | B35C49FE15B6BC5E00ED5A08 /* Localizable.strings in Resources */, 176 | B321A96C18B8EF29002B3EAD /* Default-568h@2x.png in Resources */, 177 | ); 178 | runOnlyForDeploymentPostprocessing = 0; 179 | }; 180 | /* End PBXResourcesBuildPhase section */ 181 | 182 | /* Begin PBXSourcesBuildPhase section */ 183 | B35C49D015B69B0000ED5A08 /* Sources */ = { 184 | isa = PBXSourcesBuildPhase; 185 | buildActionMask = 2147483647; 186 | files = ( 187 | B35C49E515B69B0000ED5A08 /* main.m in Sources */, 188 | B35C49E915B69B0000ED5A08 /* AppDelegate.m in Sources */, 189 | B35C49F715B69B1C00ED5A08 /* JJPluralForm.m in Sources */, 190 | B35C49FB15B6B97600ED5A08 /* LocalizationViewController.m in Sources */, 191 | ); 192 | runOnlyForDeploymentPostprocessing = 0; 193 | }; 194 | /* End PBXSourcesBuildPhase section */ 195 | 196 | /* Begin PBXVariantGroup section */ 197 | B35C49E115B69B0000ED5A08 /* InfoPlist.strings */ = { 198 | isa = PBXVariantGroup; 199 | children = ( 200 | B35C49E215B69B0000ED5A08 /* en */, 201 | ); 202 | name = InfoPlist.strings; 203 | sourceTree = ""; 204 | }; 205 | B35C4A0015B6BC5E00ED5A08 /* Localizable.strings */ = { 206 | isa = PBXVariantGroup; 207 | children = ( 208 | B35C49FF15B6BC5E00ED5A08 /* en */, 209 | B35C4A9B15B6C8F700ED5A08 /* zh-Hans */, 210 | B35C4AA315B6CBAD00ED5A08 /* ru */, 211 | B35C4AA415B6CDF200ED5A08 /* ar */, 212 | ); 213 | name = Localizable.strings; 214 | sourceTree = ""; 215 | }; 216 | /* End PBXVariantGroup section */ 217 | 218 | /* Begin XCBuildConfiguration section */ 219 | B35C49F015B69B0000ED5A08 /* Debug */ = { 220 | isa = XCBuildConfiguration; 221 | buildSettings = { 222 | ALWAYS_SEARCH_USER_PATHS = NO; 223 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 224 | CLANG_CXX_LIBRARY = "libc++"; 225 | CLANG_ENABLE_OBJC_ARC = YES; 226 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 227 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 228 | COPY_PHASE_STRIP = NO; 229 | GCC_C_LANGUAGE_STANDARD = gnu99; 230 | GCC_DYNAMIC_NO_PIC = NO; 231 | GCC_OPTIMIZATION_LEVEL = 0; 232 | GCC_PREPROCESSOR_DEFINITIONS = ( 233 | "DEBUG=1", 234 | "$(inherited)", 235 | ); 236 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 237 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 238 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 239 | GCC_WARN_UNUSED_VARIABLE = YES; 240 | IPHONEOS_DEPLOYMENT_TARGET = 6.0; 241 | ONLY_ACTIVE_ARCH = YES; 242 | SDKROOT = iphoneos; 243 | }; 244 | name = Debug; 245 | }; 246 | B35C49F115B69B0000ED5A08 /* Release */ = { 247 | isa = XCBuildConfiguration; 248 | buildSettings = { 249 | ALWAYS_SEARCH_USER_PATHS = NO; 250 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 251 | CLANG_CXX_LIBRARY = "libc++"; 252 | CLANG_ENABLE_OBJC_ARC = YES; 253 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 254 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 255 | COPY_PHASE_STRIP = YES; 256 | GCC_C_LANGUAGE_STANDARD = gnu99; 257 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 258 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 259 | GCC_WARN_UNUSED_VARIABLE = YES; 260 | IPHONEOS_DEPLOYMENT_TARGET = 6.0; 261 | OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; 262 | SDKROOT = iphoneos; 263 | VALIDATE_PRODUCT = YES; 264 | }; 265 | name = Release; 266 | }; 267 | B35C49F315B69B0000ED5A08 /* Debug */ = { 268 | isa = XCBuildConfiguration; 269 | buildSettings = { 270 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 271 | GCC_PREFIX_HEADER = "JJPluralFormExample/JJPluralFormExample-Prefix.pch"; 272 | INFOPLIST_FILE = "JJPluralFormExample/JJPluralFormExample-Info.plist"; 273 | PRODUCT_NAME = "$(TARGET_NAME)"; 274 | WRAPPER_EXTENSION = app; 275 | }; 276 | name = Debug; 277 | }; 278 | B35C49F415B69B0000ED5A08 /* Release */ = { 279 | isa = XCBuildConfiguration; 280 | buildSettings = { 281 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 282 | GCC_PREFIX_HEADER = "JJPluralFormExample/JJPluralFormExample-Prefix.pch"; 283 | INFOPLIST_FILE = "JJPluralFormExample/JJPluralFormExample-Info.plist"; 284 | PRODUCT_NAME = "$(TARGET_NAME)"; 285 | WRAPPER_EXTENSION = app; 286 | }; 287 | name = Release; 288 | }; 289 | /* End XCBuildConfiguration section */ 290 | 291 | /* Begin XCConfigurationList section */ 292 | B35C49CE15B69B0000ED5A08 /* Build configuration list for PBXProject "JJPluralFormExample" */ = { 293 | isa = XCConfigurationList; 294 | buildConfigurations = ( 295 | B35C49F015B69B0000ED5A08 /* Debug */, 296 | B35C49F115B69B0000ED5A08 /* Release */, 297 | ); 298 | defaultConfigurationIsVisible = 0; 299 | defaultConfigurationName = Release; 300 | }; 301 | B35C49F215B69B0000ED5A08 /* Build configuration list for PBXNativeTarget "JJPluralFormExample" */ = { 302 | isa = XCConfigurationList; 303 | buildConfigurations = ( 304 | B35C49F315B69B0000ED5A08 /* Debug */, 305 | B35C49F415B69B0000ED5A08 /* Release */, 306 | ); 307 | defaultConfigurationIsVisible = 0; 308 | defaultConfigurationName = Release; 309 | }; 310 | /* End XCConfigurationList section */ 311 | }; 312 | rootObject = B35C49CB15B69B0000ED5A08 /* Project object */; 313 | } 314 | -------------------------------------------------------------------------------- /JJPluralFormExample/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // JJPluralFormExample 4 | // 5 | // Created by Lin Junjie (Clean Shaven Apps Pte. Ltd.) on 22/2/14. 6 | // Copyright (c) 2014 Lin Junjie. All rights reserved. 7 | // 8 | // This Source Code Form is subject to the terms of the Mozilla Public 9 | // License, v. 2.0. If a copy of the MPL was not distributed with this 10 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 11 | 12 | #import 13 | 14 | @class LocalizationViewController; 15 | 16 | @interface AppDelegate : UIResponder 17 | 18 | @property (nonatomic, strong) UIWindow *window; 19 | @property (nonatomic, strong) LocalizationViewController *viewController; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /JJPluralFormExample/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // JJPluralFormExample 4 | // 5 | // Created by Lin Junjie (Clean Shaven Apps Pte. Ltd.) on 22/2/14. 6 | // Copyright (c) 2014 Lin Junjie. All rights reserved. 7 | // 8 | // This Source Code Form is subject to the terms of the Mozilla Public 9 | // License, v. 2.0. If a copy of the MPL was not distributed with this 10 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 11 | 12 | #import "AppDelegate.h" 13 | #import "LocalizationViewController.h" 14 | 15 | @implementation AppDelegate 16 | 17 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 18 | { 19 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 20 | 21 | self.viewController = 22 | [[LocalizationViewController alloc] initWithStyle:UITableViewStylePlain]; 23 | 24 | UINavigationController *navigationController = 25 | [[UINavigationController alloc] initWithRootViewController:self.viewController]; 26 | 27 | self.window.rootViewController = navigationController; 28 | [self.window makeKeyAndVisible]; 29 | return YES; 30 | } 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /JJPluralFormExample/JJPluralFormExample-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | com.linjunjie.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 2.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 2.0 25 | LSRequiresIPhoneOS 26 | 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /JJPluralFormExample/JJPluralFormExample-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'JJPluralFormExample' target in the 'JJPluralFormExample' project 3 | // 4 | 5 | #import 6 | 7 | #ifndef __IPHONE_4_0 8 | #warning "This project uses features only available in iOS SDK 4.0 and later." 9 | #endif 10 | 11 | #ifdef __OBJC__ 12 | #import 13 | #import 14 | #endif 15 | -------------------------------------------------------------------------------- /JJPluralFormExample/LocalizationViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // LocalizationViewController.h 3 | // JJPluralFormExample 4 | // 5 | // Created by Lin Junjie (Clean Shaven Apps Pte. Ltd.) on 22/2/14. 6 | // Copyright (c) 2014 Lin Junjie. All rights reserved. 7 | // 8 | // This Source Code Form is subject to the terms of the Mozilla Public 9 | // License, v. 2.0. If a copy of the MPL was not distributed with this 10 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 11 | 12 | #import 13 | 14 | @interface LocalizationViewController : UITableViewController 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /JJPluralFormExample/LocalizationViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // LocalizationViewController.m 3 | // JJPluralFormExample 4 | // 5 | // Created by Lin Junjie (Clean Shaven Apps Pte. Ltd.) on 22/2/14. 6 | // Copyright (c) 2014 Lin Junjie. All rights reserved. 7 | // 8 | // This Source Code Form is subject to the terms of the Mozilla Public 9 | // License, v. 2.0. If a copy of the MPL was not distributed with this 10 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 11 | 12 | #import "LocalizationViewController.h" 13 | #import "JJPluralForm.h" 14 | 15 | #define kLocalizedNDays NSLocalizedString(@"N_DAYS_PLURAL_STRING", @"N day(s)") 16 | #define kLocalizedEveryNDays NSLocalizedString(@"EVERY_N_DAYS_PLURAL_STRING", @"Every N day(s)") 17 | 18 | static NSString *CellIdentifier = @"Cell"; 19 | 20 | @interface LocalizationViewController () 21 | @property (copy, nonatomic) NSNumberFormatter *groupSeparatedFormatter; 22 | @end 23 | 24 | @implementation LocalizationViewController 25 | 26 | - (id)initWithStyle:(UITableViewStyle)style 27 | { 28 | self = [super initWithStyle:style]; 29 | if (self) 30 | { 31 | self.title = @"JJPluralForm"; 32 | [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:CellIdentifier]; 33 | 34 | // Set up a default plural rule to use for the convenience method 35 | // + pluralStringForNumber:withPluralForms: 36 | [JJPluralForm setPluralRule:[kJJPluralFormRule integerValue]]; 37 | } 38 | return self; 39 | } 40 | 41 | #pragma mark - Lazy Accessor 42 | 43 | - (NSNumberFormatter *)groupSeparatedFormatter 44 | { 45 | if (_groupSeparatedFormatter) 46 | { 47 | return _groupSeparatedFormatter; 48 | } 49 | 50 | NSNumberFormatter *formatter = [NSNumberFormatter new]; 51 | [formatter setNumberStyle:NSNumberFormatterNoStyle]; 52 | [formatter setUsesGroupingSeparator:YES]; 53 | [formatter setGroupingSeparator:[[NSLocale currentLocale] objectForKey:NSLocaleGroupingSeparator]]; 54 | [formatter setGroupingSize:3]; 55 | 56 | _groupSeparatedFormatter = [formatter copy]; 57 | return _groupSeparatedFormatter; 58 | } 59 | 60 | #pragma mark - Table view data source 61 | 62 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 63 | { 64 | return 3; 65 | } 66 | 67 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 68 | { 69 | return 10; 70 | } 71 | 72 | - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section 73 | { 74 | NSString* title = nil; 75 | 76 | switch (section) 77 | { 78 | case 0: 79 | { 80 | title = @"N day(s) using default separator"; 81 | break; 82 | } 83 | case 1: 84 | { 85 | title = @"Every N day(s) using custom separator"; 86 | break; 87 | } 88 | case 2: 89 | { 90 | title = @"N day(s) using custom number formatter"; 91 | break; 92 | } 93 | default: 94 | break; 95 | } 96 | 97 | return title; 98 | } 99 | 100 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 101 | { 102 | 103 | UITableViewCell *cell = 104 | [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath]; 105 | 106 | NSString* cellLabel = nil; 107 | 108 | switch (indexPath.section) 109 | { 110 | case 0: 111 | { 112 | cellLabel = 113 | [JJPluralForm pluralStringForNumber:indexPath.row + 1 114 | withPluralForms:kLocalizedNDays]; 115 | break; 116 | } 117 | 118 | case 1: 119 | { 120 | // Because 'kLocalizedEveryNDays' is using a custom separator, 121 | // use the longer convenience method here to specify the custom 122 | // separator. 123 | cellLabel = 124 | [JJPluralForm pluralStringForNumber:indexPath.row + 1 125 | withPluralForms:kLocalizedEveryNDays 126 | separatedBy:@"|" 127 | usingPluralRule:[kJJPluralFormRule integerValue] 128 | localizeNumeral:YES]; 129 | break; 130 | } 131 | 132 | case 2: 133 | { 134 | // Use a custom number formatter to format the large numbers 135 | cellLabel = 136 | [JJPluralForm pluralStringForNumber:(indexPath.row + 1) * 10000 137 | withPluralForms:kLocalizedNDays 138 | separatedBy:[JJPluralForm pluralFormsSeparator] 139 | usingPluralRule:[kJJPluralFormRule integerValue] 140 | numberFormatter:self.groupSeparatedFormatter]; 141 | break; 142 | } 143 | 144 | default: 145 | break; 146 | } 147 | 148 | cell.textLabel.text = cellLabel; 149 | 150 | return cell; 151 | } 152 | 153 | #pragma mark - Table view delegate 154 | 155 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 156 | { 157 | [tableView deselectRowAtIndexPath:indexPath animated:YES]; 158 | } 159 | 160 | @end 161 | -------------------------------------------------------------------------------- /JJPluralFormExample/ar.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | JJPluralFormExample 4 | 5 | Created by Lin Junjie (Clean Shaven Apps Pte. Ltd.) on 22/2/14. 6 | Copyright (c) 2014 Lin Junjie. All rights reserved. 7 | 8 | This Source Code Form is subject to the terms of the Mozilla Public 9 | License, v. 2.0. If a copy of the MPL was not distributed with this 10 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 11 | */ 12 | 13 | // Rule #12, 6 forms 14 | // Arabic 15 | // 1) is 1 16 | // 2) is 2 17 | // 3) ends in 03-10 18 | // 4) everything else but 0, ends in 00-02, excluding 0-2 19 | // 5) ends in 00-02, excluding 0-2 20 | // 6) is 0 21 | "JJ_PLURAL_FORM_RULE" = 22 | "12"; 23 | 24 | "N_DAYS_PLURAL_STRING" = 25 | "يوم %@;يومين %@;أيام %@;يوم %@;أيام %@;يوم %@"; 26 | 27 | // This plural string uses a pipe '|' instead of semicolon as separator. 28 | // Thus we need to use this method -[JJPluralForm pluralStringForNumber: 29 | // withPluralForms:separatedBy:usingPluralRule:localizeNumeral:] 30 | // to specify the custom separator when localizing this string. 31 | "EVERY_N_DAYS_PLURAL_STRING" = 32 | "يومياً|كل %@ يومين|كل %@ أيام|كل %@ يوم|كل %@ أيام|كل %@ يوم"; -------------------------------------------------------------------------------- /JJPluralFormExample/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /JJPluralFormExample/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | JJPluralFormExample 4 | 5 | Created by Lin Junjie (Clean Shaven Apps Pte. Ltd.) on 18/7/12. 6 | Copyright (c) 2012 Lin Junjie. All rights reserved. 7 | 8 | This Source Code Form is subject to the terms of the Mozilla Public 9 | License, v. 2.0. If a copy of the MPL was not distributed with this 10 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 11 | */ 12 | 13 | // Rule #1, 2 forms 14 | // English 15 | // 1) is 1 16 | // 2) everything else 17 | "JJ_PLURAL_FORM_RULE" = 18 | "1"; 19 | 20 | "N_DAYS_PLURAL_STRING" = 21 | "%@ day;%@ days"; 22 | 23 | // This plural string uses a pipe '|' instead of semicolon as separator. 24 | // Thus we need to use this method -[JJPluralForm pluralStringForNumber: 25 | // withPluralForms:separatedBy:usingPluralRule:localizeNumeral:] 26 | // to specify the custom separator when localizing this string. 27 | "EVERY_N_DAYS_PLURAL_STRING" = 28 | "Daily|Every %@ days"; -------------------------------------------------------------------------------- /JJPluralFormExample/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // JJPluralFormExample 4 | // 5 | // Created by Lin Junjie (Clean Shaven Apps Pte. Ltd.) on 22/2/14. 6 | // Copyright (c) 2014 Lin Junjie. All rights reserved. 7 | // 8 | // This Source Code Form is subject to the terms of the Mozilla Public 9 | // License, v. 2.0. If a copy of the MPL was not distributed with this 10 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 11 | 12 | #import 13 | 14 | #import "AppDelegate.h" 15 | 16 | int main(int argc, char *argv[]) 17 | { 18 | @autoreleasepool { 19 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /JJPluralFormExample/ru.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | JJPluralFormExample 4 | 5 | Created by Lin Junjie (Clean Shaven Apps Pte. Ltd.) on 22/2/14. 6 | Copyright (c) 2014 Lin Junjie. All rights reserved. 7 | 8 | This Source Code Form is subject to the terms of the Mozilla Public 9 | License, v. 2.0. If a copy of the MPL was not distributed with this 10 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 11 | */ 12 | 13 | // Specifies the plural rule form for this language 14 | // Rule #7, 3 forms 15 | // Russian 16 | // 1) ends in 1, excluding 11 17 | // 2) ends in 2-4, excluding 12-14 18 | // 3) everything else 19 | "JJ_PLURAL_FORM_RULE" = 20 | "7"; 21 | 22 | "N_DAYS_PLURAL_STRING" = 23 | "%@ день;%@ дня;%@ дней"; 24 | 25 | // This plural string uses a pipe '|' instead of semicolon as separator. 26 | // Thus we need to use this method -[JJPluralForm pluralStringForNumber: 27 | // withPluralForms:separatedBy:usingPluralRule:localizeNumeral:] 28 | // to specify the custom separator when localizing this string. 29 | "EVERY_N_DAYS_PLURAL_STRING" = 30 | "Каждый %@ день|Каждые %@ дня|Каждые %@ дней"; -------------------------------------------------------------------------------- /JJPluralFormExample/zh-Hans.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | JJPluralFormExample 4 | 5 | Created by Lin Junjie (Clean Shaven Apps Pte. Ltd.) on 22/2/14. 6 | Copyright (c) 2014 Lin Junjie. All rights reserved. 7 | 8 | This Source Code Form is subject to the terms of the Mozilla Public 9 | License, v. 2.0. If a copy of the MPL was not distributed with this 10 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 11 | */ 12 | 13 | // Specifies the plural rule form for this language 14 | // Rule #100, extended from 1 to 3 forms 15 | // Extended Chinese 16 | // 1) is 1 17 | // 2) is 2 18 | // 3) everything else 19 | "JJ_PLURAL_FORM_RULE" = 20 | "100"; 21 | 22 | "N_DAYS_PLURAL_STRING" = 23 | "%@ 天;%@ 天;%@ 天"; 24 | 25 | // This plural string uses a pipe '|' instead of semicolon as separator. 26 | // Thus we need to use this method -[JJPluralForm pluralStringForNumber: 27 | // withPluralForms:separatedBy:usingPluralRule:localizeNumeral:] 28 | // to specify the custom separator when localizing this string. 29 | "EVERY_N_DAYS_PLURAL_STRING" = 30 | "每天|每两天|每 %@ 天"; -------------------------------------------------------------------------------- /JJPluralForms.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junjie/JJPluralForm/bd051b253a2c0286c2f1c70a5901f56d4943a02f/JJPluralForms.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JJPluralForm 2.1 2 | by Lin Junjie ([@jjlin](http://twitter.com/jjlin)) 3 | 4 | ![JJPluralForm Example Project Screenshot](https://github.com/junjie/JJPluralForm/raw/master/JJPluralForms.png) 5 | 6 | ## What is JJPluralForm? 7 | 8 | In English, a word can either be singular or plural (e.g. 1 day, 2 days, 10 days). Some languages like Chinese have only one form (eg. 1 天, 2 天, 10 天), while others like Russian have three (1 день, 2 дня, 10 дней). 9 | 10 | Mozilla has codified a set of 17 plural rules across all languages. The rules can be found over here: [https://developer.mozilla.org/en/Localization_and_Plurals](https://developer.mozilla.org/en/Localization_and_Plurals) 11 | 12 | `JJPluralForm` is adapted from Mozilla's `PluralForm` to handle plural forms in your Objective-C projects. 13 | 14 | Certain plural rules have been extended to include additional plural rules. For example, even though Chinese usually have only one plural form, some scenarios call for up to three different plural forms. 15 | 16 | For example, the phrase 'Every N months' can be localized in English as 'Every 1 month', 'Every 2 months' and 'Every 6 months'. Or more naturally, it can be localized as 'Monthly', 'Every 2 months' and 'Every 6 months'. 17 | 18 | In Chinese, this would be '每月', '每两个月' and '每 6 个月'. A single plural form would result in an unnatural sounding phrase such as '每 2 个月' to a native speaker. 19 | 20 | 21 | ## Usage 22 | 23 | ### Import 24 | 25 | Import the `JJPluralForm` header file to any implementation file that requires localizing of plural forms. 26 | 27 | #import "JJPluralForm.h" 28 | 29 | ### Choose a Plural Rule 30 | 31 | Pick a suitable rule from `JJPluralForm.h`, and add it to the top of each Localizable.strings file. 32 | 33 | For example, adding the following: 34 | 35 | "JJ_PLURAL_FORM_RULE" = 36 | "1"; 37 | 38 | to the English version of `Localizable.strings` file would specify that the English localization file uses plural rule number 1, which has 2 forms: 1) is one, and 2) everything else. 39 | 40 | A convenience macro `kJJPluralFormRule` is defined in `JJPluralForm.h`, which macro can be used to obtain the plural rule as defined in your `Localizable.strings`. Since this macro returns a string, you'd need to convert this to an integer with `[kJJPluralFormRule integerValue]` when passing the rule to `JJPluralForm`. 41 | 42 | ### Setting a Default Plural Rule (2.0) 43 | 44 | Starting from `JJPluralForm` 2.0, you can configure `JJPluralForm` with a default plural rule with: 45 | 46 | [JJPluralForm setPluralRule:[kJJPluralFormRule integerValue]]; 47 | 48 | This allows you to use the new method `+pluralStringForNumber:withPluralForms:` without specifying the plural rule each time you need to localize a string. 49 | 50 | ### Localizing a Phrase 51 | 52 | To localize a phrase such as 'N day(s)', provide all plural forms in the `Localizable.strings` file in the order as listed in `JJPluralForm.h`, separated by semicolons: 53 | 54 | "N_DAYS_PLURAL_STRING" = 55 | "%@ day;%@ days"; 56 | 57 | To obtain the correct plural form for the 'N day(s)' expression, use either: 58 | 59 | [JJPluralForm pluralStringForNumber:N 60 | withPluralForms:NSLocalizedString(@"N_DAYS_PLURAL_STRING", @"") 61 | usingPluralRule:[kJJPluralFormRule integerValue] 62 | localizeNumeral:YES]; 63 | 64 | where `N` is the qualifying number. If you'd like the numbers in the returned string to be localized in the current region format, pass `YES` to localizeNumeral. This primarily affects locales using a different set of numeral symbols such as Arabic. 65 | 66 | If you've earlier configured a default plural rule with `+setPluralRule:`, you can use the shorter method to obtain the correct plural form without having to specify the plural rule each time: 67 | 68 | [JJPluralForm pluralStringForNumber:N 69 | withPluralForms:NSLocalizedString(@"N_DAYS_PLURAL_STRING", @"")]; 70 | 71 | ### Using a Custom Separator (2.0) 72 | 73 | By default, `JJPluralForm` uses semicolons to separate plural forms. New in 2.0, you can specify a custom separator. For example, if `N_DAYS_PLURAL_STRING` is separated by pipe character (`|`), i.e. `%@ day|%@ days`, you can use this method to obtain the correct plural form: 74 | 75 | [JJPluralForm pluralStringForNumber:N 76 | withPluralForms:NSLocalizedString(@"N_DAYS_PLURAL_STRING", @"") 77 | separatedBy:@"|" 78 | usingPluralRule:[kJJPluralFormRule integerValue] 79 | localizeNumeral:YES]; 80 | 81 | ### Using a Custom Number Formatter (2.1) 82 | 83 | By default, `JJPluralForms` initializes and caches an `NSNumberFormatter` set to a `numberStyle` of `NSNumberFormatterNoStyle`. Despite the style name, formatting numbers with this formatter has the effect of making numerals appear in the correct script of the region (eg. [Eastern Arabic numerals](http://en.wikipedia.org/wiki/Eastern_Arabic_numerals) for Arabic users). 84 | 85 | New in 2.1, you can provide your own cached number formatter using `+setDefaultNumberFormatter:`. This allows you to customize all aspects of how numbers are formatted when they are localized with `JJPluralForm`, for instance, by separating groups of numbers (eg. 1,000,000 vs 1000000). 86 | 87 | When calling the following method 88 | 89 | - pluralStringForNumber:withPluralForms: 90 | 91 | the number is formatted and localized with the cached number formatter unless it has been disabled with `+setShouldLocalizeNumeral:`. When calling the following methods 92 | 93 | - pluralStringForNumber:withPluralForms:usingPluralRule:localizeNumeral: 94 | - pluralStringForNumber:withPluralForms:separatedBy:usingPluralRule:localizeNumeral: 95 | 96 | the number is formatted and localized with the cached number formatter only if `localizedNumeral` is `YES`. 97 | 98 | New in 2.1, you can provide an ad-hoc `NSNumberFormatter` to format the number for each call of `pluralStringForNumber:` using the following method: 99 | 100 | - pluralStringForNumber:withPluralForms:separatedBy:usingPluralRule:numberFormatter: 101 | 102 | The sample code further illustrates the use of this method. 103 | 104 | 105 | ## CocoaPods 106 | 107 | pod 'JJPluralForm', '~> 2.1' 108 | 109 | 110 | ## Downloading the code 111 | 112 | The code can be downloaded at: [https://github.com/junjie/JJPluralForm](https://github.com/junjie/JJPluralForm) 113 | 114 | 115 | ## License 116 | 117 | This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 118 | 119 | 120 | ## Suggested Attribution Format 121 | 122 | Attribution in your app's About page is appreciated. 123 | 124 | **Includes [JJPluralForm](https://github.com/junjie/JJPluralForm) code by Lin Junjie** --------------------------------------------------------------------------------