├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── DPLocalization.podspec ├── DPLocalization ├── DPAutolocalizationProxy.m ├── DPFormattedValue.m ├── DPLocalizationBundle.m ├── DPLocalizationManager.m ├── NSAttributedString+DPLocalization.m ├── NSObject+DPLocalization.m ├── Plural+DPLocalization.m ├── dp_gen_plural.c └── include │ ├── DPAutolocalizationProxy.h │ ├── DPFormattedValue.h │ ├── DPLocalization.h │ ├── DPLocalizationBundle.h │ ├── DPLocalizationManager.h │ ├── DPLocalizationPlatforms.h │ ├── NSAttributedString+DPLocalization.h │ ├── NSObject+DPLocalization.h │ ├── Plural+DPLocalization.h │ └── dp_gen_plural.h ├── Example ├── CustomBundle │ ├── de.lproj │ │ ├── Localizable.strings │ │ ├── Localizable1.strings │ │ └── image.png │ ├── en.lproj │ │ ├── Localizable.strings │ │ ├── Localizable.stringsdict │ │ ├── Localizable1.strings │ │ └── image.png │ └── ru.lproj │ │ ├── Localizable.strings │ │ ├── Localizable.stringsdict │ │ ├── Localizable1.strings │ │ └── image.png ├── DPLocalization-OSX │ ├── DPLocalization-OSX.h │ └── Info.plist ├── DPLocalization-iOS │ ├── DPLocalizationVer.h │ └── Info.plist ├── LocalizationDemo-osx │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Images.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Info.plist │ ├── MainMenu.xib │ ├── de.lproj │ │ ├── Localizable.strings │ │ └── image.png │ ├── en.lproj │ │ ├── Localizable.strings │ │ └── image.png │ ├── main.m │ └── ru.lproj │ │ ├── Localizable.strings │ │ └── image.png ├── LocalizationDemo.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ ├── xcbaselines │ │ └── B294056B1AA439180049BF70.xcbaseline │ │ │ ├── C26C7201-8BA0-43BA-A803-AD7C23969A4E.plist │ │ │ └── Info.plist │ │ └── xcschemes │ │ ├── DPLocalization-OSX.xcscheme │ │ ├── DPLocalization-iOS.xcscheme │ │ ├── LocalizationDemo-osx.xcscheme │ │ ├── LocalizationDemo.xcscheme │ │ └── LocalizationDemoSwift.xcscheme ├── LocalizationDemo │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── LaunchImage.launchimage │ │ │ └── Contents.json │ ├── Launch Screen.xib │ ├── LocalizationDemo-Info.plist │ ├── LocalizationDemo-Prefix.pch │ ├── Main.storyboard │ ├── SecondViewController.h │ ├── SecondViewController.m │ ├── Settings.bundle │ │ └── Root.plist │ ├── ViewController.h │ ├── ViewController.m │ ├── de.lproj │ │ ├── Localizable.strings │ │ ├── Localizable1.strings │ │ └── image.png │ ├── en.lproj │ │ ├── Localizable.strings │ │ ├── Localizable.stringsdict │ │ ├── Localizable1.strings │ │ └── image.png │ ├── main.m │ └── ru.lproj │ │ ├── Localizable.strings │ │ ├── Localizable.stringsdict │ │ ├── Localizable1.strings │ │ └── image.png ├── LocalizationDemoSwift │ ├── AppDelegate.swift │ ├── Info.plist │ ├── Main.storyboard │ └── ViewController.swift └── Tests │ ├── BasicManagerTests.m │ ├── BasicNSObjectTests.m │ ├── BasicProxyTests.m │ ├── TestObject.h │ ├── TestObject.m │ └── iOS │ ├── Info.plist │ └── UIKitExtensionTests.m ├── Generator ├── parser.py └── plurals.xml ├── LICENSE ├── Package.swift ├── README.md └── dpstrings.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | .DS_Store 3 | */build/* 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | profile 14 | *.moved-aside 15 | DerivedData 16 | .idea/ 17 | *.hmap 18 | *.xccheckout 19 | 20 | #CocoaPods 21 | Pods 22 | .swiftpm 23 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode9 3 | script: xcodebuild -project Example/LocalizationDemo.xcodeproj -scheme LocalizationDemo clean build test ONLY_ACTIVE_ARCH=NO CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO -destination 'platform=iOS Simulator,name=iPhone SE,OS=11.0' -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # DPLocalization CHANGELOG 2 | 3 | ## 1.13 4 | - Added 'Swift Package Mannager' support 5 | 6 | ## 1.12.1 7 | - Fixed issue: Image localization don't use default bundle if language not selected 8 | 9 | ## 1.12 10 | - Increase minimum iOS version to 8.0 11 | - Fixed issue: ```-[DPLocalizationManager localizedImageNamed:]``` always return value from ```mainBundle``` instead of ```defaultBundle```. 12 | 13 | ## 1.11 14 | - Added 'monospace' trait 15 | 16 | ## 1.10 17 | - Added kern attribute 18 | - Added linespacing attribute 19 | 20 | ## 1.9.3 21 | - Use string from mainBundle if it was not found in default 22 | 23 | ## 1.9.1 24 | - Fixed issue with paragraph style 25 | 26 | ## 1.9 27 | - Added spacing attribute 28 | - Added alignement attribute 29 | - Added image replacer 30 | 31 | ## 1.8.3 32 | - Added nullability 33 | - Added Swift project example 34 | 35 | ## 1.8.2 36 | - Fixed https://github.com/nullic/DPLocalizationManager/issues/10 37 | 38 | ## 1.8.1 39 | - Added +[DPAutolocalizationProxy autolocalizingLocale] 40 | - Added +[DPFormattedValue formattedValueWithValue: formatter: locale:] 41 | 42 | ## 1.8 43 | - Fixed Set current language to 'nil' before reading 44 | - Added defaultBundle property. ".strings" resources can be stored outside mainBundle. 45 | - Added [DPLocalizationBundle class] 46 | 47 | ## 1.7 48 | - Added Plural rules (NSNumber - integer only, DPFormattedNumberValue - decimal) 49 | - Added -[DPLocalizationManager localizedStringForKey: table: arguments:] 50 | - Added [DPFormattedValue class] 51 | - Added -[UISegmentedControl autolocalizationPrefixKey] 52 | 53 | ## 1.6.2 54 | - Fixed UITextView incorrect text alignment 55 | 56 | ## 1.6.1 57 | - Fixed Set current language to 'nil' before reading 58 | - Added 'Proxy object' now may be used as element od arguments array (see -[setupAutolocalizationWithKey: keyPath: arguments:]) 59 | - Added Support 'u' and 's' traits attribute for iOS < 7.0 60 | - Added DPAutolocalizedStringFromTable(key, tableName, comment) function 61 | 62 | ## 1.6 63 | - Added Multiple string tables support 64 | 65 | ## 1.5 66 | - Added OS X support 67 | - Added Attributed strings format (experemental) 68 | 69 | ## 1.4.2 70 | - Swift support (macro -> "C" function) 71 | 72 | ## 1.4.1 73 | - Added DPAutolocalizedString(key, comment) macro 74 | 75 | ## 1.4 76 | - Added IBInspectable macro 77 | - Bug fixes 78 | 79 | ## 1.3 80 | - Added autolocalizationImageName property for UIImageView 81 | - Added dp_get_language_display_name(lang) and dp_get_current_language_display_name() macros 82 | - Added proxy objects around NSString and UIImage 83 | 84 | ## 1.2 85 | - Added future for using localization with custom localization file 86 | 87 | ## 1.1 88 | - Added ability to change language from application settings 89 | - New methods: +[DPLocalizationManager supportedLanguages] and +[DPLocalizationManager preferredLanguage] 90 | 91 | ## 1.0 92 | Initial release. 93 | -------------------------------------------------------------------------------- /DPLocalization.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | 3 | s.name = "DPLocalization" 4 | s.version = "1.13" 5 | s.summary = "Provides way to change localization inside application" 6 | s.homepage = "https://github.com/nullic/DPLocalizationManager" 7 | s.license = "MIT" 8 | s.author = { "Dmitriy Petrusevich" => "nullic@gmail.com" } 9 | s.platforms = { :ios => "8.0", :osx => "10.11" } 10 | 11 | s.source = { :git => "https://github.com/nullic/DPLocalizationManager.git", :tag => "1.13" } 12 | s.source_files = "DPLocalization", "DPLocalization/*.{h,m,c}", "DPLocalization/include/*.h" 13 | s.requires_arc = true 14 | 15 | end 16 | -------------------------------------------------------------------------------- /DPLocalization/DPAutolocalizationProxy.m: -------------------------------------------------------------------------------- 1 | // 2 | // DPAutolocalizationProxy.m 3 | // LocalizationDemo 4 | // 5 | // Created by dmitriy.petrusevich on 07.03.14. 6 | // Copyright (c) 2014 Dmitriy Petrusevich. All rights reserved. 7 | // 8 | 9 | #import "DPAutolocalizationProxy.h" 10 | #import "DPLocalizationManager.h" 11 | 12 | 13 | static NSString * const kLocalizationKeyKey = @"key"; 14 | static NSString * const kLocalizationTableKey = @"table"; 15 | static NSString * const kLocalizationImageNameKey = @"imageName"; 16 | static NSString * const kLocalizationResourseNameKey = @"resourseName"; 17 | static NSString * const kLocalizationResourseTypeKey = @"resourseType"; 18 | static NSString * const kLocalizationBundleKey = @"bundle"; 19 | 20 | @interface DPAutolocalizationProxy () { 21 | @protected 22 | id surrogate; 23 | NSString *locale; 24 | } 25 | @property (nonatomic, copy) NSDictionary *options; 26 | @end 27 | 28 | 29 | #pragma mark - Concrete - 30 | 31 | @interface DPAutolocalizingString : DPAutolocalizationProxy 32 | @end 33 | 34 | @implementation DPAutolocalizingString 35 | 36 | - (id)surrogate { 37 | @synchronized(self) { 38 | if (!self->surrogate) { 39 | self->surrogate = DPLocalizedStringFromTable(self.options[kLocalizationKeyKey], self.options[kLocalizationTableKey], nil); 40 | } 41 | return self->surrogate; 42 | } 43 | } 44 | 45 | @end 46 | 47 | #pragma mark - 48 | 49 | @interface DPAutolocalizingPath : DPAutolocalizationProxy 50 | @end 51 | 52 | @implementation DPAutolocalizingPath 53 | 54 | - (id)surrogate { 55 | @synchronized(self) { 56 | if (!self->surrogate) { 57 | self->surrogate = [[DPLocalizationManager currentManager] localizedPathForResource:self.options[kLocalizationResourseNameKey] ofType:self.options[kLocalizationResourseTypeKey] bundle:self.options[kLocalizationBundleKey]]; 58 | } 59 | return self->surrogate; 60 | } 61 | } 62 | 63 | @end 64 | 65 | #pragma mark - 66 | 67 | @interface DPAutolocalizingImage : DPAutolocalizationProxy 68 | @end 69 | 70 | @implementation DPAutolocalizingImage 71 | 72 | - (id)surrogate { 73 | @synchronized(self) { 74 | if (!self->surrogate) { 75 | self->surrogate = [[DPLocalizationManager currentManager] localizedImageNamed:self.options[kLocalizationImageNameKey]]; 76 | } 77 | return self->surrogate; 78 | } 79 | } 80 | 81 | @end 82 | 83 | #pragma mark - 84 | 85 | @interface DPAutolocalizingLocale : DPAutolocalizationProxy 86 | @end 87 | 88 | @implementation DPAutolocalizingLocale 89 | 90 | - (id)surrogate { 91 | @synchronized(self) { 92 | if (!self->surrogate) { 93 | self->surrogate = dp_get_current_language() ? [NSLocale localeWithLocaleIdentifier:dp_get_current_language()] : [NSLocale currentLocale]; 94 | } 95 | return self->surrogate; 96 | } 97 | } 98 | 99 | @end 100 | 101 | 102 | #pragma mark - Abstract - 103 | 104 | @implementation DPAutolocalizationProxy 105 | 106 | + (NSNotificationCenter *)notificationCenter { 107 | static dispatch_once_t onceToken; 108 | static NSNotificationCenter *notificationCenter = nil; 109 | dispatch_once(&onceToken, ^{ 110 | notificationCenter = [[NSNotificationCenter alloc] init]; 111 | }); 112 | return notificationCenter; 113 | } 114 | 115 | #pragma mark - 116 | 117 | + (NSString *)autolocalizingStringWithLocalizationKey:(NSString *)localizationKey { 118 | return [self autolocalizingStringWithLocalizationKey:localizationKey tableName:nil]; 119 | } 120 | 121 | + (NSString *)autolocalizingStringWithLocalizationKey:(NSString *)localizationKey tableName:(NSString *)tableName { 122 | NSParameterAssert(localizationKey != nil); 123 | 124 | NSMutableDictionary *options = [NSMutableDictionary dictionaryWithCapacity:2]; 125 | [options setValue:localizationKey forKey:kLocalizationKeyKey]; 126 | [options setValue:tableName forKey:kLocalizationTableKey]; 127 | 128 | return (NSString *)[[DPAutolocalizingString alloc] initWithOptions:options]; 129 | } 130 | 131 | + (NSString *)autolocalizingPathForResource:(NSString *)name ofType:(NSString *)ext inBundle:(NSBundle *)bundle { 132 | NSParameterAssert(!(name == nil && ext == nil)); 133 | 134 | NSMutableDictionary *opts = [NSMutableDictionary dictionary]; 135 | [opts setValue:name forKey:kLocalizationResourseNameKey]; 136 | [opts setValue:ext forKey:kLocalizationResourseTypeKey]; 137 | [opts setValue:bundle forKey:kLocalizationBundleKey]; 138 | 139 | return (NSString *)[[DPAutolocalizingPath alloc] initWithOptions:opts]; 140 | } 141 | 142 | + (DPImage *)autolocalizingImageNamed:(NSString *)imageName { 143 | NSParameterAssert(imageName != nil); 144 | return (DPImage *)[[DPAutolocalizingImage alloc] initWithOptions:@{kLocalizationImageNameKey : imageName}]; 145 | } 146 | 147 | + (NSLocale *)autolocalizingLocale { 148 | return (NSLocale *)[[DPAutolocalizingLocale alloc] initWithOptions:nil]; 149 | } 150 | 151 | #pragma mark - 152 | 153 | + (instancetype)alloc { 154 | DPAutolocalizationProxy *result = [super alloc]; 155 | if (result) { 156 | [[self notificationCenter] addObserver:result selector:@selector(languageDidChangeNotification:) name:DPLanguageDidChangeNotification object:nil]; 157 | } 158 | return result; 159 | } 160 | 161 | - (instancetype)initWithOptions:(NSDictionary *)options { 162 | self.options = options; 163 | self.surrogate = nil; 164 | return self; 165 | } 166 | 167 | - (void)dealloc { 168 | [[[self class] notificationCenter] removeObserver:self]; 169 | } 170 | 171 | #pragma mark - 172 | 173 | - (NSString *)description { 174 | return [self.surrogate description]; 175 | } 176 | 177 | - (NSString *)debugDescription { 178 | return [NSString stringWithFormat:@"%@ {locale = \"%@\"; value = \"%@\"}", [super description], self->locale, [self.surrogate debugDescription]]; 179 | } 180 | 181 | - (BOOL)isKindOfClass:(Class)aClass { 182 | return [self.surrogate isKindOfClass:aClass]; 183 | } 184 | 185 | - (BOOL)isMemberOfClass:(Class)aClass { 186 | return [self.surrogate isMemberOfClass:aClass]; 187 | } 188 | 189 | - (void)forwardInvocation:(NSInvocation *)invocation { 190 | invocation.target = self.surrogate; 191 | [invocation invoke]; 192 | } 193 | 194 | - (BOOL)respondsToSelector:(SEL)selector { 195 | return [self.surrogate respondsToSelector:selector]; 196 | } 197 | 198 | - (NSMethodSignature*)methodSignatureForSelector:(SEL)selector { 199 | return [self.surrogate methodSignatureForSelector:selector]; 200 | } 201 | 202 | #pragma mark - Properties 203 | 204 | - (void)setSurrogate:(id)aSurrogate { 205 | @synchronized(self) { 206 | self->surrogate = aSurrogate; 207 | self->locale = dp_get_current_language(); 208 | } 209 | } 210 | 211 | - (id)surrogate { 212 | @synchronized(self) { 213 | return self->surrogate; 214 | } 215 | } 216 | 217 | #pragma mark - Notifications 218 | 219 | - (void)languageDidChangeNotification:(NSNotification *)notification { 220 | self.surrogate = nil; 221 | } 222 | 223 | #pragma mark - Copying 224 | 225 | - (id)copy { 226 | return [self copyWithZone:nil]; 227 | } 228 | 229 | - (id)copyWithZone:(NSZone *)zone { 230 | typeof(self) result = [[self class] alloc]; 231 | result.options = [self.options copy]; 232 | return result; 233 | } 234 | 235 | @end 236 | -------------------------------------------------------------------------------- /DPLocalization/DPFormattedValue.m: -------------------------------------------------------------------------------- 1 | // 2 | // DPFormattedValue.m 3 | // DP Commons 4 | // 5 | // Created by Dmitriy Petrusevich on 30/06/15. 6 | // Copyright © 2015 Dmitriy Petrusevich. All rights reserved. 7 | // 8 | 9 | #import "DPFormattedValue.h" 10 | 11 | @interface DPFormattedValue () 12 | @property (nonatomic, copy) NSFormatter *formatter; 13 | @property (nonatomic, copy) NSLocale *locale; 14 | @property (nonatomic, copy) id value; 15 | @property (nonatomic, copy) NSString *formattedValue; 16 | @end 17 | 18 | @interface DPFormattedDateValue : DPFormattedValue 19 | @property (nonatomic, copy) NSDateFormatter *formatter; 20 | @property (nonatomic, copy) NSDate *value; 21 | @end 22 | 23 | @interface DPFormattedNumberValue : DPFormattedValue 24 | @property (nonatomic, copy) NSNumberFormatter *formatter; 25 | @property (nonatomic, copy) NSNumber *value; 26 | @end 27 | 28 | #pragma mark - DPFormattedValue 29 | 30 | @implementation DPFormattedValue 31 | 32 | + (instancetype)formattedValueWithValue:(id)value formatter:(NSFormatter *)formatter { 33 | return [self formattedValueWithValue:value formatter:formatter locale:nil]; 34 | } 35 | 36 | + (instancetype)formattedValueWithValue:(id)value formatter:(NSFormatter *)formatter locale:(NSLocale *)locale { 37 | DPFormattedValue *result = nil; 38 | 39 | if ([value isKindOfClass:[NSNumber class]] && [formatter isKindOfClass:[NSNumberFormatter class]]) { 40 | result = [[DPFormattedNumberValue alloc] init]; 41 | } 42 | else if ([value isKindOfClass:[NSDate class]] && [formatter isKindOfClass:[NSDateFormatter class]]) { 43 | result = [[DPFormattedDateValue alloc] init]; 44 | } 45 | else { 46 | result = [[self alloc] init]; 47 | } 48 | 49 | result.formatter = formatter; 50 | result.value = value; 51 | result.locale = locale; 52 | 53 | return result; 54 | } 55 | 56 | - (NSString *)formattedValue { 57 | if (_formattedValue == nil) _formattedValue = [self.formatter stringForObjectValue:self.value]; 58 | return _formattedValue; 59 | } 60 | 61 | - (NSString *)description { 62 | return [self descriptionWithLocale:self.locale]; 63 | } 64 | 65 | - (NSString *)descriptionWithLocale:(NSLocale *)locale { 66 | return self.formattedValue; 67 | } 68 | 69 | @end 70 | 71 | #pragma mark - DPFormattedDateValue 72 | 73 | @implementation DPFormattedDateValue 74 | 75 | @dynamic value; 76 | @dynamic formatter; 77 | 78 | - (NSString *)descriptionWithLocale:(NSLocale *)locale { 79 | if (locale == nil) { 80 | return [super descriptionWithLocale:locale]; 81 | } 82 | else { 83 | NSLocale *currentFormatterLocale = self.formatter.locale; 84 | 85 | self.formatter.locale = locale; 86 | NSString *result = [self.formatter stringForObjectValue:self.value]; 87 | self.formatter.locale = currentFormatterLocale; 88 | 89 | return result; 90 | } 91 | } 92 | 93 | @end 94 | 95 | #pragma mark - DPFormattedNumberValue 96 | 97 | @implementation DPFormattedNumberValue 98 | 99 | @dynamic value; 100 | @dynamic formatter; 101 | 102 | - (NSString *)descriptionWithLocale:(NSLocale *)locale { 103 | if (locale == nil) { 104 | return [super descriptionWithLocale:locale]; 105 | } 106 | else { 107 | NSLocale *currentFormatterLocale = self.formatter.locale; 108 | 109 | self.formatter.locale = locale; 110 | NSString *result = [self.formatter stringForObjectValue:self.value]; 111 | self.formatter.locale = currentFormatterLocale; 112 | 113 | return result; 114 | } 115 | } 116 | 117 | @end 118 | -------------------------------------------------------------------------------- /DPLocalization/DPLocalizationBundle.m: -------------------------------------------------------------------------------- 1 | // 2 | // DPLocalizationBundle.m 3 | // LocalizationDemo 4 | // 5 | // Created by Dmitriy Petrusevich on 15/09/15. 6 | // Copyright © 2015 Dmitriy Petrusevich. All rights reserved. 7 | // 8 | 9 | #import "DPLocalizationBundle.h" 10 | 11 | @implementation DPLocalizationBundle 12 | 13 | + (instancetype)defaultBundle { 14 | @synchronized([DPLocalizationBundle class]) { 15 | static DPLocalizationBundle *bundle = nil; 16 | 17 | if (bundle == nil) { 18 | NSString *appSupp = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES)[0]; 19 | NSString *fullPath = [appSupp stringByAppendingPathComponent:@"ind.dmitriy-petrusevich.localization"]; 20 | 21 | if ([[NSFileManager defaultManager] fileExistsAtPath:fullPath] == NO) { 22 | NSError *error = nil; 23 | [[NSFileManager defaultManager] createDirectoryAtPath:fullPath withIntermediateDirectories:YES attributes:nil error:&error]; 24 | NSAssert(error == nil, @"Unexpected error: %@", error); 25 | } 26 | 27 | bundle = [self bundleWithPath:fullPath]; 28 | } 29 | 30 | return bundle; 31 | } 32 | } 33 | 34 | - (BOOL)setStringsTable:(NSDictionary *)table withName:(NSString *)tableName language:(NSString *)languageCode error:(NSError **)inout_error { 35 | @synchronized([DPLocalizationBundle class]) { 36 | NSError *error = nil; 37 | 38 | NSString *filename = [(tableName ?: @"Localizable") stringByAppendingString:@".strings"]; 39 | NSString *fullFolderPath = self.bundlePath; 40 | 41 | if (languageCode) { 42 | NSString *langFolder = [languageCode stringByAppendingString:@".lproj"]; 43 | fullFolderPath = [fullFolderPath stringByAppendingPathComponent:langFolder]; 44 | } 45 | 46 | NSString *fullFilePath = [fullFolderPath stringByAppendingPathComponent:filename]; 47 | 48 | if (table) { 49 | if ([[NSFileManager defaultManager] fileExistsAtPath:fullFolderPath] == NO) { 50 | [[NSFileManager defaultManager] createDirectoryAtPath:fullFolderPath withIntermediateDirectories:YES attributes:nil error:&error]; 51 | } 52 | 53 | if (error == nil && [[NSFileManager defaultManager] fileExistsAtPath:fullFilePath]) { 54 | [[NSFileManager defaultManager] removeItemAtPath:fullFilePath error:&error]; 55 | } 56 | 57 | if (error == nil) { 58 | NSString *strings = [table descriptionInStringsFileFormat]; 59 | [strings writeToFile:fullFilePath atomically:YES encoding:NSUTF8StringEncoding error:&error]; 60 | } 61 | } 62 | else { 63 | if ([[NSFileManager defaultManager] fileExistsAtPath:fullFilePath]) { 64 | [[NSFileManager defaultManager] removeItemAtPath:fullFilePath error:&error]; 65 | } 66 | } 67 | 68 | 69 | if (inout_error && error) *inout_error = error; 70 | return (error == nil); 71 | } 72 | } 73 | 74 | - (NSDictionary *)stringsTableWithName:(NSString *)tableName language:(NSString *)languageCode { 75 | @synchronized([DPLocalizationBundle class]) { 76 | NSString *filename = [(tableName ?: @"Localizable") stringByAppendingString:@".strings"]; 77 | NSString *fullFolderPath = self.bundlePath; 78 | 79 | if (languageCode) { 80 | NSString *langFolder = [languageCode stringByAppendingString:@".lproj"]; 81 | fullFolderPath = [fullFolderPath stringByAppendingPathComponent:langFolder]; 82 | } 83 | 84 | NSString *fullFilePath = [fullFolderPath stringByAppendingPathComponent:filename]; 85 | return [NSDictionary dictionaryWithContentsOfFile:fullFilePath]; 86 | } 87 | } 88 | 89 | @end 90 | -------------------------------------------------------------------------------- /DPLocalization/DPLocalizationManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // DPLocalizationManager.m 3 | // DP Commons 4 | // 5 | // Created by dmitriy.petrusevich on 21.08.13. 6 | // Copyright (c) 2013 Dmitriy Petrusevich. All rights reserved. 7 | // 8 | 9 | #import "DPLocalizationManager.h" 10 | #import "Plural+DPLocalization.h" 11 | #import "DPAutolocalizationProxy.h" 12 | #import "NSObject+DPLocalization.h" 13 | 14 | 15 | NSString * const DPLanguageDidChangeNotification = @"DPLanguageDidChangeNotification"; 16 | NSString * const DPLanguagePreferenceKey = @"DPLanguageKey"; 17 | 18 | @interface DPLocalizationManager () 19 | @property (nonatomic, strong) NSMutableDictionary *tables; 20 | @property (nonatomic, strong) NSMutableDictionary *pluralRuleTables; 21 | @property (nonatomic) dp_plural_rules_func plural_rules_func; 22 | @end 23 | 24 | @implementation DPLocalizationManager 25 | 26 | @synthesize currentLanguage = _currentLanguage; 27 | @synthesize defaultStringTableName = _defaultStringTableName; 28 | @synthesize defaultBundle = _defaultBundle; 29 | 30 | - (NSString *)currentLanguage { 31 | if (!_currentLanguage) { 32 | NSString *value = [[NSUserDefaults standardUserDefaults] objectForKey:DPLanguagePreferenceKey]; 33 | value = ([value isKindOfClass:[NSString class]]) ? [value stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] : nil; 34 | _currentLanguage = (value.length == 0) ? nil : value; 35 | } 36 | return _currentLanguage; 37 | } 38 | 39 | - (void)setCurrentLanguage:(NSString *)currentLanguage { 40 | NSString *newLanguage = ([currentLanguage isKindOfClass:[NSString class]]) ? [currentLanguage stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] : nil; 41 | newLanguage = (newLanguage.length == 0) ? nil : newLanguage; 42 | NSString *curLang = _currentLanguage; 43 | 44 | if (newLanguage != curLang && !(newLanguage && [curLang isEqualToString:newLanguage])) { 45 | _currentLanguage = newLanguage; 46 | [[NSUserDefaults standardUserDefaults] setObject:newLanguage forKey:DPLanguagePreferenceKey]; 47 | [[NSUserDefaults standardUserDefaults] synchronize]; 48 | 49 | [self.tables removeAllObjects]; 50 | [self.pluralRuleTables removeAllObjects]; 51 | self.plural_rules_func = newLanguage ? dp_plural_rules_for_lang_code(newLanguage) : NULL; 52 | [[DPAutolocalizationProxy notificationCenter] postNotificationName:DPLanguageDidChangeNotification object:self]; 53 | [[NSNotificationCenter defaultCenter] postNotificationName:DPLanguageDidChangeNotification object:self]; 54 | } 55 | } 56 | 57 | - (NSString *)usedLanguage { 58 | return self.currentLanguage ? self.currentLanguage : [self.defaultBundle preferredLanguage]; 59 | } 60 | 61 | - (dp_plural_rules_func)plural_rules_func { 62 | if (_plural_rules_func == NULL) { 63 | _plural_rules_func = dp_plural_rules_for_lang_code(self.usedLanguage); 64 | } 65 | return _plural_rules_func; 66 | } 67 | 68 | - (NSMutableDictionary *)tables { 69 | if (!_tables) _tables = [[NSMutableDictionary alloc] init]; 70 | return _tables; 71 | } 72 | 73 | - (NSMutableDictionary *)pluralRuleTables { 74 | if (_pluralRuleTables == nil) _pluralRuleTables = [[NSMutableDictionary alloc] init]; 75 | return _pluralRuleTables; 76 | } 77 | 78 | - (void)loadTableNamedIfNeeded:(NSString *)tableName { 79 | if (tableName && self.tables[tableName] == nil) { 80 | { 81 | NSString *path = [self.defaultBundle pathForResource:tableName ofType:@"strings" inDirectory:nil forLocalization:self.currentLanguage]; 82 | path = path ? path : [self.defaultBundle pathForResource:tableName ofType:@"strings"]; 83 | NSDictionary *tableContent = path ? [NSDictionary dictionaryWithContentsOfFile:path] : nil; 84 | self.tables[tableName] = tableContent ? tableContent : @{}; 85 | } 86 | 87 | { 88 | NSString *path = [self.defaultBundle pathForResource:tableName ofType:@"stringsdict" inDirectory:nil forLocalization:self.currentLanguage]; 89 | path = path ? path : [self.defaultBundle pathForResource:tableName ofType:@"stringsdict"]; 90 | NSDictionary *tableContent = path ? [NSDictionary dictionaryWithContentsOfFile:path] : nil; 91 | self.pluralRuleTables[tableName] = tableContent ? tableContent : @{}; 92 | } 93 | } 94 | } 95 | 96 | - (NSString *)defaultStringTableName { 97 | return _defaultStringTableName ? _defaultStringTableName : @"Localizable"; 98 | } 99 | 100 | - (void)setDefaultStringTableName:(NSString *)defaultStringTableName { 101 | if ([defaultStringTableName isEqualToString:[self defaultStringTableName]] == NO) { 102 | _defaultStringTableName = [defaultStringTableName copy]; 103 | 104 | [[DPAutolocalizationProxy notificationCenter] postNotificationName:DPLanguageDidChangeNotification object:self]; 105 | [[NSNotificationCenter defaultCenter] postNotificationName:DPLanguageDidChangeNotification object:self]; 106 | } 107 | } 108 | 109 | - (NSBundle *)defaultBundle { 110 | return _defaultBundle ? _defaultBundle : [NSBundle mainBundle]; 111 | } 112 | 113 | - (void)setDefaultBundle:(NSBundle *)defaultBundle { 114 | if (_defaultBundle != defaultBundle) { 115 | _defaultBundle = defaultBundle; 116 | 117 | [self.tables removeAllObjects]; 118 | [self.pluralRuleTables removeAllObjects]; 119 | [[DPAutolocalizationProxy notificationCenter] postNotificationName:DPLanguageDidChangeNotification object:self]; 120 | [[NSNotificationCenter defaultCenter] postNotificationName:DPLanguageDidChangeNotification object:self]; 121 | } 122 | } 123 | 124 | #pragma mark - 125 | 126 | + (instancetype)currentManager { 127 | static DPLocalizationManager *_sharedInstance = nil; 128 | static dispatch_once_t onceToken = 0; 129 | 130 | dispatch_once(&onceToken, ^{ 131 | _sharedInstance = [[DPLocalizationManager alloc] init]; 132 | }); 133 | 134 | return _sharedInstance; 135 | } 136 | 137 | - (instancetype)init { 138 | self = [super init]; 139 | if (self) { 140 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(userDefaultsDidChangeNotification:) name:NSUserDefaultsDidChangeNotification object:nil]; 141 | } 142 | return self; 143 | } 144 | 145 | - (void)dealloc { 146 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 147 | } 148 | 149 | #pragma mark - Notifications 150 | 151 | - (void)userDefaultsDidChangeNotification:(NSNotification *)notification { 152 | [self setCurrentLanguage:[[NSUserDefaults standardUserDefaults] objectForKey:DPLanguagePreferenceKey]]; 153 | } 154 | 155 | #pragma mark - Localization 156 | 157 | - (NSString *)localizedStringForKey:(NSString *)key { 158 | return [self localizedStringForKey:key table:self.defaultStringTableName]; 159 | } 160 | 161 | - (NSString *)localizedStringForKey:(NSString *)key table:(NSString *)table { 162 | NSParameterAssert([key isKindOfClass:[NSString class]]); 163 | NSParameterAssert([table isKindOfClass:[NSString class]] || table == nil); 164 | 165 | NSString *tableName = [table length] ? table : self.defaultStringTableName; 166 | [self loadTableNamedIfNeeded:tableName]; 167 | 168 | NSString *result = self.tables[tableName][key]; 169 | if (result == nil) { 170 | return [[NSBundle mainBundle] localizedStringForKey:key value:@"" table:tableName]; 171 | } 172 | return result; 173 | } 174 | 175 | - (NSString *)localizedStringForKey:(NSString *)key table:(NSString *)table arguments:(NSArray *)arguments { 176 | NSString *tableName = [table length] ? table : self.defaultStringTableName; 177 | 178 | NSString *resultString = [self localizedStringForKey:key table:tableName]; 179 | 180 | if (arguments.count > 0) { 181 | NSDictionary *pluralRules = self.pluralRuleTables[tableName][key]; 182 | 183 | if (pluralRules != nil) { 184 | static NSRegularExpression *regexp = nil; 185 | static dispatch_once_t onceToken; 186 | dispatch_once(&onceToken, ^{ 187 | regexp = [NSRegularExpression regularExpressionWithPattern:@"%([0-9]+\\$)??#@(.+?)@" options:kNilOptions error:nil]; 188 | }); 189 | 190 | NSString *format = pluralRules[@"NSStringLocalizedFormatKey"]; 191 | NSMutableString *mutableStr = [format mutableCopy]; 192 | 193 | NSArray *matches = [regexp matchesInString:format options:kNilOptions range:NSMakeRange(0, format.length)]; 194 | [matches enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(NSTextCheckingResult *match, NSUInteger idx, BOOL *stop) { 195 | NSUInteger usedIndex = idx; 196 | NSRange indexRandge = [match rangeAtIndex:1]; 197 | if (indexRandge.location != NSNotFound) { 198 | NSString *index = [resultString substringWithRange:indexRandge]; 199 | usedIndex = ([index integerValue] - 1); 200 | } 201 | 202 | id argument = (arguments.count > usedIndex) ? arguments[usedIndex] : nil; 203 | 204 | NSString *argName = [mutableStr substringWithRange:[match rangeAtIndex:2]]; 205 | NSString *string = [self _substitudePluralStringWithValue:argument variants:pluralRules[argName]]; 206 | [mutableStr replaceCharactersInRange:match.range withString:string]; 207 | }]; 208 | 209 | resultString = mutableStr; 210 | } 211 | 212 | static NSRegularExpression *regexp = nil; 213 | static dispatch_once_t onceToken; 214 | dispatch_once(&onceToken, ^{ 215 | regexp = [NSRegularExpression regularExpressionWithPattern:@"%([0-9]+\\$)??@" options:kNilOptions error:nil]; 216 | }); 217 | 218 | NSArray *matches = [regexp matchesInString:resultString options:kNilOptions range:NSMakeRange(0, resultString.length)]; 219 | NSMutableString *mutableStr = [resultString mutableCopy]; 220 | [matches enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(NSTextCheckingResult *match, NSUInteger idx, BOOL *stop) { 221 | NSUInteger usedIndex = idx; 222 | NSRange indexRandge = [match rangeAtIndex:1]; 223 | if (indexRandge.location != NSNotFound) { 224 | NSString *index = [resultString substringWithRange:indexRandge]; 225 | usedIndex = ([index integerValue] - 1); 226 | } 227 | 228 | id subs = (arguments.count > usedIndex) ? arguments[usedIndex] : nil; 229 | [mutableStr replaceCharactersInRange:match.range withString:[subs description]]; 230 | }]; 231 | 232 | resultString = mutableStr; 233 | } 234 | 235 | return resultString; 236 | } 237 | 238 | - (DPImage *)localizedImageNamed:(NSString *)name { 239 | DPImage *result = nil; 240 | 241 | if (name && self.currentLanguage) { 242 | NSString *localizationPath = [NSString stringWithFormat:@"%@.lproj", self.currentLanguage]; 243 | #if DPLocalization_UIKit 244 | NSString *imageNamePath = [localizationPath stringByAppendingPathComponent:name]; 245 | result = [DPImage imageNamed:imageNamePath inBundle:[self defaultBundle] compatibleWithTraitCollection:nil]; 246 | #elif DPLocalization_AppKit 247 | NSString *resourcePath = [self.defaultBundle resourcePath] ?: self.defaultBundle.bundlePath; 248 | NSString *bundlePath = [resourcePath stringByAppendingPathComponent:localizationPath]; 249 | result = [[NSBundle bundleWithPath:bundlePath] imageForResource:name]; 250 | #endif 251 | } 252 | 253 | #if DPLocalization_UIKit 254 | return result ? result : [DPImage imageNamed:name inBundle:[self defaultBundle] compatibleWithTraitCollection:nil]; 255 | #elif DPLocalization_AppKit 256 | return result ? result : [[self defaultBundle] imageForResource:name]; 257 | #endif 258 | } 259 | 260 | - (NSString *)localizedPathForResource:(NSString *)name ofType:(NSString *)extension bundle:(NSBundle *)bundle { 261 | NSBundle *searchBundle = bundle ? bundle : self.defaultBundle; 262 | NSString *path = [searchBundle pathForResource:name ofType:extension inDirectory:nil forLocalization:self.currentLanguage]; 263 | path = path ? path : [searchBundle pathForResource:name ofType:extension]; 264 | return path; 265 | } 266 | 267 | #pragma mark - Utils 268 | 269 | - (NSString *)_substitudePluralStringWithValue:(NSNumber *)value variants:(NSDictionary *)variants { 270 | enum DPPluralRule rule = [value pluralRuleWithRules:self.plural_rules_func]; 271 | 272 | NSString *result = variants[dp_key_from_pluralrule(rule)]; 273 | if (result == nil) { 274 | NSLog(@"WARNING: Can't find '%@'", dp_key_from_pluralrule(rule)); 275 | result = variants[dp_key_from_pluralrule(DPPluralRuleUnknown)]; 276 | } 277 | 278 | return result; 279 | } 280 | 281 | #pragma mark - Languages 282 | 283 | + (NSArray *)supportedLanguages { 284 | return [[NSBundle mainBundle] localizations]; 285 | } 286 | 287 | + (NSString *)preferredLanguage { 288 | return [[NSBundle mainBundle] preferredLanguage]; 289 | } 290 | 291 | #pragma mark - Deprecated 292 | 293 | - (void)setLocalizationFileName:(NSString *)localizationFileName { 294 | [self setDefaultStringTableName:localizationFileName]; 295 | } 296 | 297 | - (NSString *)localizationFileName { 298 | return [self defaultStringTableName]; 299 | } 300 | 301 | @end 302 | 303 | 304 | #pragma mark - 305 | #pragma mark "C" Functions 306 | #pragma mark - 307 | 308 | 309 | NSString * DPLocalizedString(NSString *key, NSString *comment) { 310 | return [[DPLocalizationManager currentManager] localizedStringForKey:key]; 311 | } 312 | 313 | NSString * DPLocalizedStringFromTable(NSString *key, NSString *table, NSString *comment) { 314 | return [[DPLocalizationManager currentManager] localizedStringForKey:key table:table]; 315 | } 316 | 317 | NSString * DPAutolocalizedString(NSString *key, NSString *comment) { 318 | return [DPAutolocalizationProxy autolocalizingStringWithLocalizationKey:key]; 319 | } 320 | 321 | NSString * DPAutolocalizedStringFromTable(NSString *key, NSString *tableName, NSString *comment) { 322 | return [DPAutolocalizationProxy autolocalizingStringWithLocalizationKey:key tableName:tableName]; 323 | } 324 | 325 | NSString * dp_get_current_language() { 326 | return [[DPLocalizationManager currentManager] currentLanguage]; 327 | } 328 | 329 | void dp_set_current_language(NSString *lang) { 330 | [[DPLocalizationManager currentManager] setCurrentLanguage:lang]; 331 | } 332 | 333 | NSString * dp_get_language_display_name(NSString *lang) { 334 | return [[[[NSLocale alloc] initWithLocaleIdentifier:lang] displayNameForKey:NSLocaleIdentifier value:lang] capitalizedString]; 335 | } 336 | 337 | NSString * dp_get_current_language_display_name() { 338 | return dp_get_language_display_name(dp_get_current_language()); 339 | } 340 | 341 | #pragma mark Deprecated 342 | 343 | NSString * dp_get_current_filename() { 344 | return [[DPLocalizationManager currentManager] localizationFileName]; 345 | } 346 | 347 | void dp_set_current_filename(NSString *filename) { 348 | [[DPLocalizationManager currentManager] setLocalizationFileName:filename]; 349 | } 350 | -------------------------------------------------------------------------------- /DPLocalization/NSAttributedString+DPLocalization.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSAttributedString+DPLocalization.m 3 | // DP Commons 4 | // 5 | // Created by dmitriy.petrusevich on 03.11.14. 6 | // Copyright (c) 2014 Dmitriy Petrusevich. All rights reserved. 7 | // 8 | 9 | #import "NSAttributedString+DPLocalization.h" 10 | #import "DPLocalizationManager.h" 11 | 12 | @implementation NSAttributedString (DPLocalization) 13 | 14 | + (NSAttributedString *)dp_attibutedStringWithString:(NSString *)string font:(DPFont *)font textColor:(DPColor *)textColor { 15 | return [self dp_attibutedStringWithString:string font:font textColor:textColor paragraphStyle:[NSParagraphStyle defaultParagraphStyle]]; 16 | } 17 | 18 | + (NSAttributedString *)dp_attibutedStringWithString:(NSString *)string font:(DPFont *)font textColor:(DPColor *)textColor paragraphStyle:(NSParagraphStyle *)paragraphStyle { 19 | NSMutableAttributedString *attrsString = nil; 20 | 21 | if (string) { 22 | static NSRegularExpression *tagRegExp = nil; 23 | static NSRegularExpression *replaceRegExp = nil; 24 | 25 | static dispatch_once_t onceToken; 26 | dispatch_once(&onceToken, ^{ 27 | tagRegExp = [NSRegularExpression regularExpressionWithPattern:@"<([^<]*?)>\\{(.*?)\\}" options:NSRegularExpressionDotMatchesLineSeparators error:nil]; 28 | replaceRegExp = [NSRegularExpression regularExpressionWithPattern:@"\\[<([^<]*?)>\\]" options:NSRegularExpressionDotMatchesLineSeparators error:nil]; 29 | }); 30 | 31 | NSMutableDictionary *attrs = [NSMutableDictionary dictionary]; 32 | [attrs setValue:font forKey:NSFontAttributeName]; 33 | [attrs setValue:textColor forKey:NSForegroundColorAttributeName]; 34 | [attrs setValue:paragraphStyle forKey:NSParagraphStyleAttributeName]; 35 | 36 | attrsString = [[NSMutableAttributedString alloc] initWithString:string attributes:attrs]; 37 | 38 | NSArray *matches = [tagRegExp matchesInString:string options:kNilOptions range:NSMakeRange(0, string.length)]; 39 | [matches enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(NSTextCheckingResult *match, NSUInteger idx, BOOL *stop) { 40 | 41 | NSString *textString = [string substringWithRange:[match rangeAtIndex:2]]; 42 | NSString *styleString = [string substringWithRange:[match rangeAtIndex:1]]; 43 | 44 | NSMutableDictionary *tagAttrs = [attrs mutableCopy]; 45 | [tagAttrs setValuesForKeysWithDictionary:[self stylesFromSting:styleString font:font paragraphStyle:paragraphStyle ?: [NSParagraphStyle defaultParagraphStyle]]]; 46 | 47 | NSAttributedString *replaceString = [[NSAttributedString alloc] initWithString:textString attributes:tagAttrs]; 48 | [attrsString replaceCharactersInRange:match.range withAttributedString:replaceString]; 49 | }]; 50 | 51 | matches = [replaceRegExp matchesInString:attrsString.string options:kNilOptions range:NSMakeRange(0, attrsString.string.length)]; 52 | [matches enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(NSTextCheckingResult *match, NSUInteger idx, BOOL *stop) { 53 | NSString *infoString = [attrsString.string substringWithRange:[match rangeAtIndex:1]]; 54 | 55 | NSDictionary *attrs = [attrsString attributesAtIndex:match.range.location effectiveRange:NULL]; 56 | DPFont *effectiveFont = attrs[NSFontAttributeName] ?: font; 57 | 58 | NSAttributedString *replaceString = [self replacementFromSting:infoString font:effectiveFont]; 59 | if (replaceString != nil) { 60 | [attrsString replaceCharactersInRange:match.range withAttributedString:replaceString]; 61 | [attrsString addAttributes:attrs range:NSMakeRange(match.range.location, replaceString.length)]; 62 | } 63 | }]; 64 | } 65 | 66 | return attrsString; 67 | } 68 | 69 | + (NSDictionary *)stylesFromSting:(NSString *)styleString font:(DPFont *)font paragraphStyle:(NSParagraphStyle *)originalParagraphStyle { 70 | NSMutableDictionary *attrs = [NSMutableDictionary dictionary]; 71 | 72 | static NSRegularExpression *nameExp = nil; 73 | static NSRegularExpression *colorExp = nil; 74 | static NSRegularExpression *sizeExp = nil; 75 | static NSRegularExpression *traitsExp = nil; 76 | static NSRegularExpression *linkExp = nil; 77 | static NSRegularExpression *spacingExp = nil; 78 | static NSRegularExpression *lineSpacingExp = nil; 79 | static NSRegularExpression *alignmentExp = nil; 80 | static NSRegularExpression *kerningExp = nil; 81 | 82 | 83 | static dispatch_once_t onceToken; 84 | dispatch_once(&onceToken, ^{ 85 | nameExp = [NSRegularExpression regularExpressionWithPattern:@"name=\"(.+?)\"" options:NSRegularExpressionCaseInsensitive error:nil]; 86 | colorExp = [NSRegularExpression regularExpressionWithPattern:@"color=([0-9]{1,3}),([0-9]{1,3}),([0-9]{1,3})(,[0-9]{1,3})?" options:NSRegularExpressionCaseInsensitive error:nil]; 87 | traitsExp = [NSRegularExpression regularExpressionWithPattern:@"traits=([!buism]+)" options:NSRegularExpressionCaseInsensitive error:nil]; 88 | sizeExp = [NSRegularExpression regularExpressionWithPattern:@"size=([0-9.]+)" options:NSRegularExpressionCaseInsensitive error:nil]; 89 | linkExp = [NSRegularExpression regularExpressionWithPattern:@"link=\"(.+?)\"" options:NSRegularExpressionCaseInsensitive error:nil]; 90 | spacingExp = [NSRegularExpression regularExpressionWithPattern:@"spacing=([0-9.]+)" options:NSRegularExpressionCaseInsensitive error:nil]; 91 | lineSpacingExp = [NSRegularExpression regularExpressionWithPattern:@"linespacing=([0-9.]+)" options:NSRegularExpressionCaseInsensitive error:nil]; 92 | alignmentExp = [NSRegularExpression regularExpressionWithPattern:@"alignment=(left|center|right|justified|natural)" options:NSRegularExpressionCaseInsensitive error:nil]; 93 | kerningExp = [NSRegularExpression regularExpressionWithPattern:@"kern=([0-9.]+)" options:NSRegularExpressionCaseInsensitive error:nil]; 94 | }); 95 | 96 | NSRange allStringRange = NSMakeRange(0, styleString.length); 97 | 98 | NSString *fontName = nil; 99 | CGFloat fontSize = 0; 100 | 101 | NSTextCheckingResult *sizeCheck = [sizeExp firstMatchInString:styleString options:kNilOptions range:allStringRange]; 102 | if (sizeCheck) fontSize = [[styleString substringWithRange:[sizeCheck rangeAtIndex:1]] floatValue]; 103 | 104 | NSTextCheckingResult *nameCheck = [nameExp firstMatchInString:styleString options:kNilOptions range:allStringRange]; 105 | if (nameCheck) fontName = [styleString substringWithRange:[nameCheck rangeAtIndex:1]]; 106 | 107 | NSTextCheckingResult *colorCheck = [colorExp firstMatchInString:styleString options:kNilOptions range:allStringRange]; 108 | if (colorCheck) { 109 | CGFloat r = [[styleString substringWithRange:[colorCheck rangeAtIndex:1]] floatValue] / (CGFloat)255.0; 110 | CGFloat g = [[styleString substringWithRange:[colorCheck rangeAtIndex:2]] floatValue] / (CGFloat)255.0; 111 | CGFloat b = [[styleString substringWithRange:[colorCheck rangeAtIndex:3]] floatValue] / (CGFloat)255.0; 112 | CGFloat a = ([colorCheck rangeAtIndex:4].location != NSNotFound) ? ([[[styleString substringWithRange:[colorCheck rangeAtIndex:4]] substringFromIndex:1] floatValue] / (CGFloat)255.0) : (CGFloat)1.0; 113 | [attrs setValue:[DPColor colorWithRed:r green:g blue:b alpha:a] forKey:NSForegroundColorAttributeName]; 114 | } 115 | 116 | NSTextCheckingResult *kerningCheck = [kerningExp firstMatchInString:styleString options:kNilOptions range:allStringRange]; 117 | if (kerningCheck) { 118 | [attrs setValue:@([[styleString substringWithRange:[kerningCheck rangeAtIndex:1]] floatValue]) forKey:NSKernAttributeName]; 119 | } 120 | 121 | NSMutableParagraphStyle *paragraphStyle = [originalParagraphStyle mutableCopy]; 122 | 123 | NSTextCheckingResult *spacingCheck = [spacingExp firstMatchInString:styleString options:kNilOptions range:allStringRange]; 124 | if (spacingCheck) { 125 | paragraphStyle.paragraphSpacing = [[styleString substringWithRange:[spacingCheck rangeAtIndex:1]] floatValue]; 126 | } 127 | 128 | NSTextCheckingResult *lineSpacingCheck = [lineSpacingExp firstMatchInString:styleString options:kNilOptions range:allStringRange]; 129 | if (lineSpacingCheck) { 130 | paragraphStyle.lineSpacing = [[styleString substringWithRange:[lineSpacingCheck rangeAtIndex:1]] floatValue]; 131 | } 132 | 133 | NSTextCheckingResult *alignmentMatch = [alignmentExp firstMatchInString:styleString options:kNilOptions range:allStringRange]; 134 | if (alignmentMatch) { 135 | NSString *alignmentValue = [styleString substringWithRange:[alignmentMatch rangeAtIndex:1]]; 136 | NSTextAlignment alignment = NSTextAlignmentNatural; 137 | if ([alignmentValue isEqualToString:@"left"]) { alignment = NSTextAlignmentLeft; } 138 | else if ([alignmentValue isEqualToString:@"center"]) { alignment = NSTextAlignmentCenter; } 139 | else if ([alignmentValue isEqualToString:@"right"]) { alignment = NSTextAlignmentRight; } 140 | else if ([alignmentValue isEqualToString:@"justified"]) { alignment = NSTextAlignmentJustified; } 141 | else if ([alignmentValue isEqualToString:@"natural"]) { alignment = NSTextAlignmentNatural; } 142 | 143 | paragraphStyle.alignment = alignment; 144 | } 145 | [attrs setValue:paragraphStyle forKey:NSParagraphStyleAttributeName]; 146 | 147 | DPFont *styleFont = font; 148 | if (fontName != nil && fontSize > 0) { 149 | styleFont = [DPFont fontWithName:fontName size:fontSize]; 150 | } 151 | else if (fontName != nil && font != nil) { 152 | styleFont = [DPFont fontWithName:fontName size:font.pointSize]; 153 | } 154 | else if (fontSize > 0 && font != nil) { 155 | #ifdef DPLocalization_UIKit 156 | styleFont = [font fontWithSize:fontSize]; 157 | #else 158 | styleFont = [DPFont fontWithName:font.fontName size:fontSize]; 159 | #endif 160 | } 161 | 162 | NSTextCheckingResult *linkCheck = [linkExp firstMatchInString:styleString options:kNilOptions range:allStringRange]; 163 | if (linkCheck) { 164 | NSString *link = [styleString substringWithRange:[linkCheck rangeAtIndex:1]]; 165 | NSURL *url = [NSURL URLWithString:link]; 166 | [attrs setValue:url ? url : link forKey:NSLinkAttributeName]; 167 | } 168 | 169 | NSTextCheckingResult *traitsCheck = [traitsExp firstMatchInString:styleString options:kNilOptions range:allStringRange]; 170 | if (traitsCheck) { 171 | NSString *traitsString = [styleString substringWithRange:[traitsCheck rangeAtIndex:1]]; 172 | DPFontSymbolicTraits traits = [[styleFont fontDescriptor] symbolicTraits]; 173 | 174 | if ([traitsString rangeOfString:@"b"].location != NSNotFound) traits |= DPFontTraitBold; 175 | if ([traitsString rangeOfString:@"i"].location != NSNotFound) traits |= DPFontTraitItalic; 176 | if ([traitsString rangeOfString:@"m"].location != NSNotFound) traits |= DPFontDescriptorTraitMonoSpace; 177 | if ([traitsString rangeOfString:@"!b"].location != NSNotFound) traits &= (~DPFontTraitBold); 178 | if ([traitsString rangeOfString:@"!i"].location != NSNotFound) traits &= (~DPFontTraitItalic); 179 | if ([traitsString rangeOfString:@"!m"].location != NSNotFound) traits &= (~DPFontDescriptorTraitMonoSpace); 180 | 181 | if ([traitsString rangeOfString:@"u"].location != NSNotFound) [attrs setValue:@(NSUnderlineStyleSingle) forKey:NSUnderlineStyleAttributeName]; 182 | if ([traitsString rangeOfString:@"s"].location != NSNotFound) [attrs setValue:@(NSUnderlineStyleSingle) forKey:NSStrikethroughStyleAttributeName]; 183 | 184 | // Not used - will be useful in futher releases 185 | if ([traitsString rangeOfString:@"!u"].location != NSNotFound) [attrs setValue:@(NSUnderlineStyleNone) forKey:NSUnderlineStyleAttributeName]; 186 | if ([traitsString rangeOfString:@"!s"].location != NSNotFound) [attrs setValue:@(NSUnderlineStyleNone) forKey:NSStrikethroughStyleAttributeName]; 187 | 188 | DPFontDescriptor *fontDescriptor = [[styleFont fontDescriptor] fontDescriptorWithSymbolicTraits:traits]; 189 | [attrs setValue:[DPFont fontWithDescriptor:fontDescriptor size:fontSize] forKey:NSFontAttributeName]; 190 | } 191 | else { 192 | [attrs setValue:styleFont forKey:NSFontAttributeName]; 193 | } 194 | 195 | return attrs; 196 | } 197 | 198 | + (NSAttributedString *)replacementFromSting:(NSString *)infoString font:(DPFont *)font { 199 | static NSRegularExpression *imageExp = nil; 200 | static dispatch_once_t onceToken; 201 | dispatch_once(&onceToken, ^{ 202 | imageExp = [NSRegularExpression regularExpressionWithPattern:@"img=\"(.+?)\"(\\ssize=\\(([0-9.]+);([0-9.]+)\\))?(\\soffset=\\(([-0-9.]+);([-0-9.]+)\\))?" options:NSRegularExpressionCaseInsensitive error:nil]; 203 | }); 204 | 205 | NSRange allStringRange = NSMakeRange(0, infoString.length); 206 | 207 | NSTextCheckingResult *imageCheck = [imageExp firstMatchInString:infoString options:kNilOptions range:allStringRange]; 208 | if (imageCheck) { 209 | NSString *imageName = [infoString substringWithRange:[imageCheck rangeAtIndex:1]]; 210 | DPImage *image = [[DPLocalizationManager currentManager] localizedImageNamed:imageName]; 211 | 212 | CGFloat width = image.size.width; 213 | CGFloat height = image.size.height; 214 | CGFloat xOffset = 0; 215 | CGFloat yOffset = font.descender; 216 | 217 | if ([imageCheck rangeAtIndex:2].location != NSNotFound) { 218 | width = [[infoString substringWithRange:[imageCheck rangeAtIndex:3]] floatValue]; 219 | height = [[infoString substringWithRange:[imageCheck rangeAtIndex:4]] floatValue]; 220 | } 221 | 222 | if ([imageCheck rangeAtIndex:5].location != NSNotFound) { 223 | xOffset = [[infoString substringWithRange:[imageCheck rangeAtIndex:6]] floatValue]; 224 | yOffset = [[infoString substringWithRange:[imageCheck rangeAtIndex:7]] floatValue]; 225 | } 226 | 227 | NSTextAttachment *textAttachment = [[NSTextAttachment alloc] init]; 228 | textAttachment.image = image; 229 | textAttachment.bounds = CGRectMake(xOffset, yOffset, width, height); 230 | 231 | return [NSAttributedString attributedStringWithAttachment:textAttachment]; 232 | } 233 | 234 | return nil; 235 | } 236 | 237 | @end 238 | 239 | 240 | -------------------------------------------------------------------------------- /DPLocalization/Plural+DPLocalization.m: -------------------------------------------------------------------------------- 1 | // 2 | // Plural+DPLocalization.m 3 | // DP Commons 4 | // 5 | // Created by Dmitriy Petrusevich on 25/06/15. 6 | // Copyright (c) 2015 Dmitriy Petrusevich. All rights reserved. 7 | // 8 | 9 | #import "Plural+DPLocalization.h" 10 | 11 | NSString *dp_key_from_pluralrule(enum DPPluralRule rule) { 12 | NSString *result = nil; 13 | 14 | switch (rule) { 15 | case DPPluralRuleUnknown: 16 | result = @"other"; 17 | break; 18 | 19 | case DPPluralRuleZero: 20 | result = @"zero"; 21 | break; 22 | case DPPluralRuleOne: 23 | result = @"one"; 24 | break; 25 | case DPPluralRuleTwo: 26 | result = @"two"; 27 | break; 28 | case DPPluralRuleFew: 29 | result = @"few"; 30 | break; 31 | case DPPluralRuleMany: 32 | result = @"many"; 33 | break; 34 | case DPPluralRuleOther: 35 | result = @"other"; 36 | break; 37 | } 38 | 39 | return result; 40 | } 41 | 42 | dp_plural_rules_func dp_plural_rules_for_lang_code(NSString *lang_code) { 43 | dp_plural_rules_func ptr = dp_plural_rules_for_lang([lang_code UTF8String]); 44 | if (ptr == NULL) { 45 | NSRange sym_range = [lang_code rangeOfString:@"_"]; 46 | if (sym_range.location != NSNotFound) { 47 | NSString *code = [lang_code substringToIndex:sym_range.location]; 48 | ptr = dp_plural_rules_for_lang([code UTF8String]); 49 | } 50 | } 51 | 52 | return ptr ? ptr : dp_plural_rules_always_other; 53 | } 54 | 55 | #pragma mark - NSNumber 56 | 57 | @implementation NSNumber (DPLocalization_Plural) 58 | 59 | - (enum DPPluralRule)pluralRuleWithLanguage:(NSString *)language { 60 | dp_plural_rules_func func = dp_plural_rules_for_lang_code(language); 61 | return [self pluralRuleWithRules:func]; 62 | } 63 | 64 | - (enum DPPluralRule)pluralRuleWithRules:(dp_plural_rules_func)rules { 65 | double n = ABS([self doubleValue]); 66 | int i = (int)n; 67 | return rules(n, i, 0, 0, 0, 0); 68 | } 69 | 70 | @end 71 | 72 | #pragma mark - DPFormattedValue 73 | 74 | @implementation DPFormattedValue (DPLocalization_Plural) 75 | 76 | - (enum DPPluralRule)pluralRuleWithLanguage:(NSString *)language { 77 | dp_plural_rules_func func = dp_plural_rules_for_lang_code(language); 78 | return [self pluralRuleWithRules:func]; 79 | } 80 | 81 | - (enum DPPluralRule)pluralRuleWithRules:(dp_plural_rules_func)rules { 82 | enum DPPluralRule result = DPPluralRuleUnknown; 83 | 84 | if ([[self value] isKindOfClass:[NSNumber class]]) { 85 | NSNumber *value = [self value]; 86 | 87 | if ([[self formatter] isKindOfClass:[NSNumberFormatter class]]) { 88 | NSNumberFormatter *formatter = (NSNumberFormatter *)[self formatter]; 89 | NSString *strValue = [self description]; 90 | NSString *decimalSeparator = [formatter decimalSeparator]; 91 | 92 | double n = ABS([value doubleValue]); 93 | int i = (int)n; 94 | int v = 0, w = 0, f = 0, t = 0; 95 | 96 | NSRange separatorRange = [strValue rangeOfString:decimalSeparator]; 97 | if (separatorRange.location != NSNotFound) { 98 | NSUInteger startPosition = separatorRange.location + separatorRange.length; 99 | NSUInteger endPosition = startPosition; 100 | 101 | for (; endPosition < strValue.length; endPosition++) { 102 | unichar ch = [strValue characterAtIndex:endPosition]; 103 | if (ch < '0' || ch > '9') { 104 | break; 105 | } 106 | } 107 | 108 | v = (int)(endPosition - startPosition); 109 | NSString *fractionalPart = [strValue substringWithRange:NSMakeRange(startPosition, v)]; 110 | f = [fractionalPart intValue]; 111 | 112 | NSInteger lastNonZeroCharacterIndex = (fractionalPart.length - 1); 113 | for (; lastNonZeroCharacterIndex >= 0; lastNonZeroCharacterIndex--) { 114 | if ([fractionalPart characterAtIndex:lastNonZeroCharacterIndex] != '0') { 115 | break; 116 | } 117 | } 118 | 119 | NSString *fractionalPartWithoutZeros = [fractionalPart substringToIndex:(lastNonZeroCharacterIndex + 1)]; 120 | w = (int)[fractionalPartWithoutZeros length]; 121 | t = [fractionalPartWithoutZeros intValue]; 122 | } 123 | 124 | 125 | return rules(n, i, v, w, f, t); 126 | } 127 | else { 128 | result = [value pluralRuleWithRules:rules]; 129 | } 130 | } 131 | 132 | return result; 133 | } 134 | 135 | @end 136 | -------------------------------------------------------------------------------- /DPLocalization/include/DPAutolocalizationProxy.h: -------------------------------------------------------------------------------- 1 | // 2 | // DPAutolocalizationProxy.h 3 | // LocalizationDemo 4 | // 5 | // Created by dmitriy.petrusevich on 07.03.14. 6 | // Copyright (c) 2014 Dmitriy Petrusevich. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "DPLocalizationPlatforms.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface DPAutolocalizationProxy : NSProxy 15 | 16 | /** 17 | @return Notification center user for deviler notification to proxy objects. 18 | @discussion DPLocalizationManager post DPLanguageDidChangeNotification notification via +[DPAutolocalizationProxy notificationCenter], then post via +[NSNotificationCenter defaultCenter]. 19 | */ 20 | + (NSNotificationCenter *)notificationCenter; 21 | 22 | /** 23 | @brief Return a localized version of a string. 24 | @param key The key for a string in the [[DPLocalizationManager currentManager] defaultStringsTableName] table. 25 | 26 | @discussion Value is equal to invoking DPLocalizedStringFromTable(key, nil, nil); 27 | @return The result is invoking of +[self autolocalizingStringWithLocalizationKey:key tableName:nil]. 28 | */ 29 | + (NSString *)autolocalizingStringWithLocalizationKey:(NSString *)key; 30 | 31 | /** 32 | @brief Return a localized version of a string. 33 | @param key The key for a string in the table identified by tableName. 34 | @param tableName The receiver’s string table to search. If tableName is nil or is an empty string, the method attempts to use the table in Localizable.strings. 35 | 36 | @discussion Value is equal to invoking DPLocalizedStringFromTable(key, tableName, nil); 37 | @return Proxy object which value depended from selected language. 38 | */ 39 | + (NSString *)autolocalizingStringWithLocalizationKey:(NSString *)key tableName:(NSString *_Nullable)tableName; 40 | 41 | /** 42 | @brief Returns the full pathname for the resource identified by the specified name and file extension for selected language. 43 | @param name The name of the resource file. 44 | @param ext If extension is an empty string or nil, the extension is assumed not to exist and the file is the first file encountered that exactly matches name. 45 | @param bundle If bundle is nil, the bundle is assumed as main bundle. 46 | 47 | @discussion Value is equal to invoking [[DPLocalizationManager currentManager] localizedPathForResource:name ofType:ext bundle:bundle]. 48 | @return Proxy object which value depended from selected language. 49 | */ 50 | + (NSString *_Nullable)autolocalizingPathForResource:(NSString *_Nullable)name ofType:(NSString *_Nullable)ext inBundle:(NSBundle *_Nullable)bundle; 51 | 52 | /** 53 | @brief Returns a localized version of an image. 54 | @param name The name associated with the desired image. 55 | 56 | @discussion Value is equal to invoking [[DPLocalizationManager currentManager] localizedImageNamed:name]. 57 | @return Proxy object which value depended from selected language. 58 | */ 59 | + (DPImage *_Nullable)autolocalizingImageNamed:(NSString *)name; 60 | 61 | + (NSLocale *)autolocalizingLocale; 62 | @end 63 | 64 | NS_ASSUME_NONNULL_END 65 | -------------------------------------------------------------------------------- /DPLocalization/include/DPFormattedValue.h: -------------------------------------------------------------------------------- 1 | // 2 | // DPFormattedValue.h 3 | // DP Commons 4 | // 5 | // Created by Dmitriy Petrusevich on 30/06/15. 6 | // Copyright © 2015 Dmitriy Petrusevich. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface DPFormattedValue : NSObject 14 | + (instancetype)formattedValueWithValue:(id)value formatter:(NSFormatter *)formatter; 15 | + (instancetype)formattedValueWithValue:(id)value formatter:(NSFormatter *)formatter locale:(NSLocale * _Nullable)locale; 16 | 17 | - (id)value; 18 | - (NSFormatter *)formatter; 19 | - (NSString *)formattedValue; 20 | 21 | - (NSString *)description; 22 | - (NSString *)descriptionWithLocale:(NSLocale *)locale; 23 | @end 24 | 25 | NS_ASSUME_NONNULL_END 26 | -------------------------------------------------------------------------------- /DPLocalization/include/DPLocalization.h: -------------------------------------------------------------------------------- 1 | // 2 | // DPLocalization.h 3 | // DP Commons 4 | // 5 | // Created by Dmitriy Petrusevich on 26/07/15. 6 | // Copyright © 2015 Dmitriy Petrusevich. All rights reserved. 7 | // 8 | 9 | #import "DPLocalizationManager.h" 10 | 11 | #import "NSObject+DPLocalization.h" 12 | #import "NSAttributedString+DPLocalization.h" 13 | 14 | #import "DPAutolocalizationProxy.h" 15 | #import "Plural+DPLocalization.h" 16 | #import "DPFormattedValue.h" 17 | #import "DPLocalizationBundle.h" -------------------------------------------------------------------------------- /DPLocalization/include/DPLocalizationBundle.h: -------------------------------------------------------------------------------- 1 | // 2 | // DPLocalizationBundle.h 3 | // LocalizationDemo 4 | // 5 | // Created by Dmitriy Petrusevich on 15/09/15. 6 | // Copyright © 2015 Dmitriy Petrusevich. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | /** 12 | @class DPLocalizationBundle 13 | @brief Helper class used for store localization strings. 14 | */ 15 | @interface DPLocalizationBundle : NSBundle 16 | /** 17 | @brief Returns shared DPLocalizationBundle object. 18 | 19 | @return Returns instance used for store locazition strings. 20 | */ 21 | + (instancetype _Nonnull)defaultBundle; 22 | 23 | /** 24 | @brief Save/Remove strings table. 25 | 26 | @param table Stings table. Table must contain only strings. 27 | @param tableName Name of strings table. "Localizable" - if value is 'nil' 28 | @param languageCode Language codes (i.e.: "en", "ru", "fr" and etc.) 29 | @param error If an error occurs, this pointer is set to an actual error object containing the error information. 30 | 31 | @return 'YES' if operation operation was succeeded. 32 | */ 33 | - (BOOL)setStringsTable:(NSDictionary *_Nullable)table withName:(NSString *_Nullable)tableName language:(NSString *_Nullable)languageCode error:(NSError *_Nullable*_Nullable)error; 34 | 35 | /** 36 | @param tableName Name of strings table. "Localizable" - if value is 'nil' 37 | @param languageCode Language codes (i.e.: "en", "ru", "fr" and etc.) 38 | 39 | @return Strings table if exist. 40 | */ 41 | - (NSDictionary *_Nullable)stringsTableWithName:(NSString *_Nullable)tableName language:(NSString *_Nullable)languageCode; 42 | 43 | @end 44 | 45 | -------------------------------------------------------------------------------- /DPLocalization/include/DPLocalizationManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // DPLocalizationManager.h 3 | // DP Commons 4 | // 5 | // Created by dmitriy.petrusevich on 21.08.13. 6 | // Copyright (c) 2013 Dmitriy Petrusevich. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "DPLocalizationPlatforms.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | /** 15 | @class DPLocalizationManager 16 | @brief Helper class used for control language of the application. 17 | */ 18 | @interface DPLocalizationManager : NSObject 19 | /** 20 | @property currentLanguage 21 | @brief Current selected language. It's same names that used for resource localization as part of .lproj folders (i.e.: "en", "ru", "fr" and etc.) or nil for system language. After currentLanguage is set, it value will be saved in [NSUserDefaults standardUserDefaults]. 22 | */ 23 | @property (nonatomic, copy) NSString * _Nullable currentLanguage; 24 | 25 | /** 26 | @property usedLanguage 27 | @brief Current selected language or [DPLocalizationManager preferredLanguage] if 'currentLanguage' not set. 28 | */ 29 | @property (nonatomic, readonly) NSString * usedLanguage; 30 | 31 | /** 32 | @property defaultStringsTableName 33 | @brief Default bundle used for localization. 34 | */ 35 | @property(nonatomic, copy, null_resettable) NSString *defaultStringTableName; 36 | 37 | /** 38 | @property defaultBundle 39 | @brief Default string table name used for localization. 40 | */ 41 | @property(nonatomic, strong, null_resettable) NSBundle *defaultBundle; 42 | 43 | /** 44 | @brief Returns shared DPLocalizationManager object. 45 | 46 | @return Returns instance used for control locazition. 47 | */ 48 | + (instancetype)currentManager; 49 | 50 | /** 51 | @brief Returns a localized version of a string. 52 | @param key The key for a string in the -[self defaultStringsTableName] table. 53 | 54 | @return The result is invoking of -[self localizedStringForKey:key table:self.defaultStringsTableName]. 55 | */ 56 | - (NSString *)localizedStringForKey:(NSString *)key; 57 | 58 | /** 59 | @brief Returns a localized version of a string. 60 | @param key The key for a string in the table identified by tableName. 61 | @param table The receiver’s string table to search. If tableName is nil or is an empty string, the method attempts to use the table in Localizable.strings. 62 | 63 | @return Returns a localized version of a string for selected language. If string not found return the result of NSLocalizedStringFromTable macro. 64 | */ 65 | - (NSString *)localizedStringForKey:(NSString *)key table:(NSString * _Nullable)table; 66 | 67 | /** 68 | @brief Returns a localized version of a string. Method use plural rules if {defaultStringTableName}.stringsdict exist. 69 | @param key The key for a string in the table identified by tableName. 70 | @param table The receiver’s string table to search. If tableName is nil or is an empty string, the method attempts to use the table in Localizable.strings. 71 | @param arguments List of arguments to substitute into localized string. 72 | 73 | @return Returns a localized version of a string for selected language. 74 | */ 75 | - (NSString *)localizedStringForKey:(NSString *)key table:(NSString * _Nullable)table arguments:(NSArray * _Nullable)arguments; 76 | 77 | /** 78 | @brief Returns a localized version of an image. 79 | @param name The name associated with the desired image. 80 | 81 | @return Returns a localized version of an image for selected language. If image not found return the result of invoking -[UIImage imageNamed:]. 82 | */ 83 | 84 | - (DPImage * _Nullable)localizedImageNamed:(NSString *)name; 85 | 86 | /** 87 | @brief Returns the full pathname for the resource identified by the specified name and file extension for selected language. 88 | @param name The name of the resource file. 89 | @param extension If extension is an empty string or nil, the extension is assumed not to exist and the file is the first file encountered that exactly matches name. 90 | @param bundle If bundle is nil, the bundle is assumed as 'default' bundle. 91 | 92 | @return The result is invoking of [bundle pathForResource:name ofType:extension inDirectory:nil forLocalization:currentLanguage]. If result is nil return [bundle pathForResource:name ofType:extension] instead. 93 | */ 94 | - (NSString * _Nullable)localizedPathForResource:(NSString * _Nullable)name ofType:(NSString * _Nullable)extension bundle:(NSBundle * _Nullable)bundle; 95 | 96 | /** 97 | @brief Array of languages that may be found inside application bundle. 98 | 99 | @return Array of language codes (i.e.: "en", "ru", "fr" and etc.) or empty array if no language found. 100 | */ 101 | + (NSArray *)supportedLanguages; 102 | 103 | /** 104 | @brief Return code for preferred application language. 105 | 106 | @return [[NSBundle mainBundle] preferredLanguage]. 107 | */ 108 | + (NSString *)preferredLanguage; 109 | 110 | @property(nonatomic, copy) NSString *localizationFileName DEPRECATED_MSG_ATTRIBUTE("Use 'defaultStringTableName' property instead. This property will be removed in further releases."); 111 | 112 | @end 113 | 114 | 115 | /** 116 | @return The result is invoking of [[DPLocalizationManager currentManager] localizedStringForKey:key]; 117 | */ 118 | NSString * DPLocalizedString(NSString *key, NSString * _Nullable comment); 119 | 120 | /** 121 | @return The result is invoking of [[DPLocalizationManager currentManager] localizedStringForKey:key table:table]; 122 | */ 123 | NSString * DPLocalizedStringFromTable(NSString *key, NSString * _Nullable table, NSString * _Nullable comment); 124 | 125 | /** 126 | @return The result is invoking of [DPAutolocalizationProxy autolocalizingStringWithLocalizationKey:key]; 127 | */ 128 | NSString * DPAutolocalizedString(NSString *key, NSString * _Nullable comment); 129 | 130 | /** 131 | @return The result is invoking of [DPAutolocalizationProxy autolocalizingStringWithLocalizationKey:key tableName:tableName]; 132 | */ 133 | NSString * DPAutolocalizedStringFromTable(NSString *key, NSString * _Nullable tableName, NSString * _Nullable comment); 134 | 135 | /** 136 | @return The result is invoking of [[DPLocalizationManager currentManager] currentLanguage]; 137 | */ 138 | NSString * _Nullable dp_get_current_language(void); 139 | 140 | /** 141 | @brief Equal to [[DPLocalizationManager currentManager] setCurrentLanguage:lang]; 142 | */ 143 | void dp_set_current_language(NSString * _Nullable lang); 144 | 145 | 146 | /** 147 | @brief Return language name that can be shown to user. 148 | @param lang Language code (i.e.: "en", "ru", "fr" and etc.) 149 | @return The result is invoking of [[[[NSLocale alloc] initWithLocaleIdentifier:lang] displayNameForKey:NSLocaleIdentifier value:lang] capitalizedString]; 150 | */ 151 | NSString * _Nullable dp_get_language_display_name(NSString *lang); 152 | 153 | /** 154 | @return The result is invoking of dp_get_language_display_name(dp_get_current_language()) 155 | */ 156 | NSString * _Nullable dp_get_current_language_display_name(void); 157 | 158 | 159 | NSString * dp_get_current_filename(void) DEPRECATED_MSG_ATTRIBUTE("Use 'defaultStringTableName' property instead. This function will be removed in further releases."); 160 | void dp_set_current_filename(NSString *filename) DEPRECATED_MSG_ATTRIBUTE("Use 'defaultStringTableName' property instead. This function will be removed in further releases."); 161 | 162 | /** 163 | @brief Notification posted by DPLocalizationManager after currentLanguage property did changed. 164 | */ 165 | extern NSString * const DPLanguageDidChangeNotification; 166 | 167 | /** 168 | @brief Key used for store selection in [NSUserDefaults standardUserDefaults] 169 | */ 170 | extern NSString * const DPLanguagePreferenceKey; 171 | 172 | NS_ASSUME_NONNULL_END 173 | -------------------------------------------------------------------------------- /DPLocalization/include/DPLocalizationPlatforms.h: -------------------------------------------------------------------------------- 1 | // 2 | // DPLocalizationPlatforms.h 3 | // DP Commons 4 | // 5 | // Created by Dmitriy Petrusevich on 17/02/15. 6 | // Copyright (c) 2015 Dmitriy Petrusevich. All rights reserved. 7 | // 8 | 9 | #if (TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR) 10 | #import 11 | 12 | #define DPLocalization_UIKit 1 13 | #define DPLocalization_AppKit 0 14 | 15 | #define DPColor UIColor 16 | #define DPImage UIImage 17 | 18 | #define DPFont UIFont 19 | #define DPFontSymbolicTraits UIFontDescriptorSymbolicTraits 20 | #define DPFontDescriptor UIFontDescriptor 21 | 22 | #define DPFontTraitBold UIFontDescriptorTraitBold 23 | #define DPFontTraitItalic UIFontDescriptorTraitItalic 24 | #define DPFontDescriptorTraitMonoSpace UIFontDescriptorTraitMonoSpace 25 | 26 | #elif TARGET_OS_MAC 27 | #import 28 | 29 | #define DPLocalization_UIKit 0 30 | #define DPLocalization_AppKit 1 31 | 32 | #define DPColor NSColor 33 | #define DPImage NSImage 34 | 35 | #define DPFont NSFont 36 | #define DPFontSymbolicTraits NSFontSymbolicTraits 37 | #define DPFontDescriptor NSFontDescriptor 38 | 39 | #define DPFontTraitBold NSFontBoldTrait 40 | #define DPFontTraitItalic NSFontItalicTrait 41 | #define DPFontDescriptorTraitMonoSpace NSFontMonoSpaceTrait 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /DPLocalization/include/NSAttributedString+DPLocalization.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSAttributedString+DPLocalization.h 3 | // DP Commons 4 | // 5 | // Created by dmitriy.petrusevich on 03.11.14. 6 | // Copyright (c) 2014 Dmitriy Petrusevich. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "DPLocalizationPlatforms.h" 11 | 12 | @interface NSAttributedString (DPLocalization) 13 | + (NSAttributedString *_Nullable)dp_attibutedStringWithString:(NSString *_Nullable)string font:(DPFont *_Nullable)font textColor:(DPColor *_Nullable)textColor; 14 | + (NSAttributedString *_Nullable)dp_attibutedStringWithString:(NSString *_Nullable)string font:(DPFont *_Nullable)font textColor:(DPColor *_Nullable)textColor paragraphStyle:(NSParagraphStyle *_Nullable)paragraphStyle; 15 | @end 16 | -------------------------------------------------------------------------------- /DPLocalization/include/NSObject+DPLocalization.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+DPLocalization.h 3 | // DP Commons 4 | // 5 | // Created by dmitriy.petrusevich on 21.08.13. 6 | // Copyright (c) 2013 Dmitriy Petrusevich. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "DPLocalizationPlatforms.h" 11 | 12 | #ifndef IBInspectable 13 | #define IBInspectable 14 | #endif 15 | 16 | NS_ASSUME_NONNULL_BEGIN 17 | 18 | /** 19 | @category NSObject (DPLocalization) 20 | @brief Add ability to automatic localization. 21 | */ 22 | @interface NSObject (DPLocalization) 23 | /** 24 | @brief Setup update value automaticaly after DPLanguageDidChangeNotification notification was posted. 25 | @param key The key for a string in the Localizable.strings table. 26 | @param keyPath Keypath for assing localized value. 27 | */ 28 | - (void)setupAutolocalizationWithKey:(NSString *_Nullable)key keyPath:(NSString *_Nullable)keyPath; 29 | 30 | /** 31 | @brief Setup update value automaticaly after DPLanguageDidChangeNotification notification was posted. 32 | @param key The key for a string in the Localizable.strings table. 33 | @param keyPath Keypath for assing localized value. 34 | @param arguments List of arguments to substitute into localized string. 35 | */ 36 | - (void)setupAutolocalizationWithKey:(NSString *_Nullable)key keyPath:(NSString *_Nullable)keyPath arguments:(NSArray *_Nullable)arguments; 37 | 38 | /** 39 | @brief Update list of arguments to substitute into localized string. 40 | @param arguments List of arguments to substitute into localized string. 41 | */ 42 | - (void)updateAutolocalizationArguments:(NSArray *_Nullable)arguments; 43 | 44 | /** 45 | @brief Remove object updating on DPLanguageDidChangeNotification notification. 46 | */ 47 | - (void)removeAutolocalization; 48 | 49 | /** 50 | @result Return YES if object configured for automatic localization and NO otherwise. 51 | */ 52 | - (BOOL)isAutolocalizationEnabled; 53 | 54 | /** 55 | @brief Called on DPLanguageDidChangeNotification notification. 56 | @discussion Invoke -[localizeWithLocalizationKey: arguments: keyPath:]. 57 | */ 58 | - (void)localize; 59 | 60 | /** 61 | @brief Perform object localization. 62 | @param key The key for a string in the Localizable.strings table. 63 | @param arguments List of arguments to substitute into localized string. 64 | @param keyPath Keypath for assing localized value. 65 | 66 | @discussion Invoke -[setLocalizedValue: forKeyPath:keyPath]. 67 | */ 68 | - (void)localizeWithLocalizationKey:(NSString *)key arguments:(NSArray *_Nullable)arguments keyPath:(NSString *)keyPath; 69 | 70 | /** 71 | @brief Called before setting new value. Default implementation invoking -[self setValue:value forKeyPath:keyPath]. 72 | @param value Localized value. 73 | @param keyPath Keypath for assing localized value. 74 | */ 75 | - (void)setLocalizedValue:(NSString *)value forKeyPath:(NSString *)keyPath; 76 | @end 77 | 78 | 79 | #if DPLocalization_UIKit 80 | 81 | //MARK: - 82 | //MARK: UIKit 83 | 84 | @interface UILabel (DPLocalization) 85 | /** 86 | @property autolocalizationKey 87 | @brief Shortcut for add automatic localization. 88 | @discussion Set value equal to invoking -[self setupAutolocalizationWithKey:autolocalizationKey keyPath:@"text"]. 89 | */ 90 | @property (nonatomic, copy, nullable) IBInspectable NSString *autolocalizationKey; 91 | @property (nonatomic) IBInspectable BOOL isAttributedKey; 92 | @end 93 | 94 | @interface UIButton (DPLocalization) 95 | /** 96 | @property autolocalizationKey 97 | @brief Shortcut for add automatic localization. 98 | @discussion Invoke -[self setTitle:localizedValue forState:UIControlStateNormal] on DPLanguageDidChangeNotification notification. 99 | */ 100 | @property (nonatomic, copy, nullable) IBInspectable NSString *autolocalizationKey; 101 | @property (nonatomic) IBInspectable BOOL isAttributedKey; 102 | @end 103 | 104 | @interface UIBarItem (DPLocalization) 105 | /** 106 | @property autolocalizationKey 107 | @brief Shortcut for add automatic localization. 108 | @discussion Set value equal to invoking -[self setupAutolocalizationWithKey:autolocalizationKey keyPath:@"title"]. 109 | */ 110 | @property (nonatomic, copy, nullable) IBInspectable NSString *autolocalizationKey; 111 | @end 112 | 113 | 114 | @interface UINavigationItem (DPLocalization) 115 | /** 116 | @property autolocalizationKey 117 | @brief Shortcut for add automatic localization. 118 | @discussion Set value equal to invoking -[self setupAutolocalizationWithKey:autolocalizationKey keyPath:@"title"]. 119 | */ 120 | @property (nonatomic, copy, nullable) IBInspectable NSString *autolocalizationKey; 121 | @end 122 | 123 | @interface UITextField (DPLocalization) 124 | /** 125 | @property autolocalizationKey 126 | @brief Shortcut for add automatic localization. 127 | @discussion Set value equal to invoking -[self setupAutolocalizationWithKey:autolocalizationKey keyPath:@"placeholder"]. 128 | */ 129 | @property (nonatomic, copy, nullable) IBInspectable NSString *autolocalizationKey; 130 | @property (nonatomic) IBInspectable BOOL isAttributedKey; 131 | @end 132 | 133 | @interface UITextView (DPLocalization) 134 | /** 135 | @property autolocalizationKey 136 | @brief Shortcut for add automatic localization. 137 | @discussion Set value equal to invoking -[self setupAutolocalizationWithKey:autolocalizationKey keyPath:@"text"]. 138 | */ 139 | @property (nonatomic, copy, nullable) IBInspectable NSString *autolocalizationKey; 140 | @property (nonatomic) IBInspectable BOOL isAttributedKey; 141 | @end 142 | 143 | @interface UISearchBar (DPLocalization) 144 | /** 145 | @property autolocalizationKey 146 | @brief Shortcut for add automatic localization. 147 | @discussion Set value equal to invoking -[self setupAutolocalizationWithKey:autolocalizationKey keyPath:@"placeholder"]. 148 | */ 149 | @property (nonatomic, copy, nullable) IBInspectable NSString *autolocalizationKey; 150 | @end 151 | 152 | @interface UISegmentedControl (DPLocalization) 153 | /** 154 | @property autolocalizationPrefixKey 155 | @brief Shortcut for add automatic localization. 156 | @discussion Invoke -[self setTitle:DPLocalizedString({autolocalizationPrefixKey}{segment}, nil) forSegmentAtIndex:{segment}] on DPLanguageDidChangeNotification notification. 157 | */ 158 | @property (nonatomic, copy, nullable) IBInspectable NSString *autolocalizationPrefixKey; 159 | @end 160 | 161 | @interface UIImageView (DPLocalization) 162 | /** 163 | @property autolocalizationImageName 164 | @brief Shortcut for add automatic localization. 165 | @discussion Invoke -[self setImage:[UIImage localizedImageNamed:self.autolocalizationImageName]] on DPLanguageDidChangeNotification notification. 166 | */ 167 | @property (nonatomic, copy, nullable) IBInspectable NSString *autolocalizationImageName; 168 | @end 169 | 170 | @interface UIViewController (DPLocalization) 171 | /** 172 | @property autolocalizationKey 173 | @brief Shortcut for add automatic localization. 174 | @discussion Set value equal to invoking -[self setupAutolocalizationWithKey:autolocalizationKey keyPath:@"title"]. 175 | */ 176 | @property (nonatomic, copy, nullable) IBInspectable NSString *autolocalizationKey; 177 | @end 178 | 179 | #endif 180 | 181 | 182 | //MARK: - 183 | //MARK: AppKit 184 | 185 | #if DPLocalization_AppKit 186 | 187 | @interface NSMenuItem (DPLocalization) 188 | /** 189 | @property autolocalizationKey 190 | @brief Shortcut for add automatic localization. 191 | @discussion Set value equal to invoking -[self setupAutolocalizationWithKey:autolocalizationKey keyPath:@"title"]. 192 | */ 193 | @property (nonatomic, copy, nullable) IBInspectable NSString *autolocalizationKey; 194 | @property (nonatomic) IBInspectable BOOL isAttributedKey; 195 | @end 196 | 197 | @interface NSMenu (DPLocalization) 198 | /** 199 | @property autolocalizationKey 200 | @brief Shortcut for add automatic localization. 201 | @discussion Set value equal to invoking -[self setupAutolocalizationWithKey:autolocalizationKey keyPath:@"title"]. 202 | */ 203 | @property (nonatomic, copy, nullable) IBInspectable NSString *autolocalizationKey; 204 | @end 205 | 206 | @interface NSButton (DPLocalization) 207 | /** 208 | @property autolocalizationKey 209 | @brief Shortcut for add automatic localization. 210 | @discussion Set value equal to invoking -[self setupAutolocalizationWithKey:autolocalizationKey keyPath:@"title"]. 211 | */ 212 | @property (nonatomic, copy, nullable) IBInspectable NSString *autolocalizationKey; 213 | @property (nonatomic) IBInspectable BOOL isAttributedKey; 214 | @end 215 | 216 | @interface NSControl (DPLocalization) 217 | /** 218 | @property autolocalizationKey 219 | @brief Shortcut for add automatic localization. 220 | @discussion Set value equal to invoking -[self setupAutolocalizationWithKey:autolocalizationKey keyPath:@"stringValue"]. 221 | */ 222 | @property (nonatomic, copy, nullable) IBInspectable NSString *autolocalizationKey; 223 | @property (nonatomic) IBInspectable BOOL isAttributedKey; 224 | @end 225 | 226 | @interface NSTextField (DPLocalization) 227 | /** 228 | @property autolocalizationKey 229 | @brief Shortcut for add automatic localization. 230 | @discussion Set value equal to invoking -[self setupAutolocalizationWithKey:autolocalizationKey keyPath:@"placeholderString"]. 231 | Cannot be mixed with -[NSControl autolocalizationKey]. 232 | */ 233 | @property (nonatomic, copy, nullable) IBInspectable NSString *placeholderAutolocalizationKey; 234 | @end 235 | 236 | @interface NSText (DPLocalization) 237 | /** 238 | @property autolocalizationKey 239 | @brief Shortcut for add automatic localization. 240 | @discussion Set value equal to invoking -[self setupAutolocalizationWithKey:autolocalizationKey keyPath:@"string"]. 241 | */ 242 | @property (nonatomic, copy, nullable) IBInspectable NSString *autolocalizationKey; 243 | @end 244 | 245 | @interface NSWindow (DPLocalization) 246 | /** 247 | @property autolocalizationKey 248 | @brief Shortcut for add automatic localization. 249 | @discussion Set value equal to invoking -[self setupAutolocalizationWithKey:autolocalizationKey keyPath:@"title"]. 250 | */ 251 | @property (nonatomic, copy, nullable) IBInspectable NSString *autolocalizationKey; 252 | @end 253 | 254 | @interface NSImageView (DPLocalization) 255 | /** 256 | @property autolocalizationImageName 257 | @brief Shortcut for add automatic localization. 258 | @discussion Invoke -[self setImage:[NSImage localizedImageNamed:self.autolocalizationImageName]] on DPLanguageDidChangeNotification notification. 259 | */ 260 | @property (nonatomic, copy, nullable) IBInspectable NSString *autolocalizationImageName; 261 | @end 262 | 263 | #endif 264 | 265 | 266 | //MARK: - 267 | //MARK: - Common 268 | 269 | @interface NSBundle (DPLocalization) 270 | /** 271 | @return The result is invoking of [[DPLocalizationManager currentManager] localizedPathForResource:name ofType:extension bundle:self]. 272 | */ 273 | - (NSString *_Nullable)localizedPathForResource:(NSString *_Nullable)name ofType:(NSString *_Nullable)extension; 274 | 275 | /** 276 | @return The result is invoking of [DPAutolocalizationProxy autolocalizingPathForResource:name ofType:extension inBundle:self]. 277 | */ 278 | - (NSString *_Nullable)autolocalizingPathForResource:(NSString *_Nullable)name ofType:(NSString *_Nullable)extension; 279 | 280 | /** 281 | @brief Return code for preferred application language. 282 | 283 | @return Return first element of [self preferredLocalizations], or [[NSBundle mainBundle] developmentLocalization] if 'preferredLocalizations' array is empty, or "en" if 'developmentLocalization' cannot be determined. 284 | */ 285 | - (NSString * _Nullable)preferredLanguage; 286 | @end 287 | 288 | @interface NSString (DPLocalization) 289 | /** 290 | @return The result is invoking of [DPAutolocalizationProxy autolocalizingStringWithLocalizationKey:localizationKey] 291 | */ 292 | + (NSString *)autolocalizingStringWithLocalizationKey:(NSString *)localizationKey; 293 | @end 294 | 295 | @interface DPImage (DPLocalization) 296 | /** 297 | @return The result is invoking of [[DPLocalizationManager currentManager] localizedImageNamed:name]. 298 | */ 299 | + (DPImage *_Nullable)localizedImageNamed:(NSString *)name; 300 | 301 | /** 302 | @return The result is invoking of [DPAutolocalizationProxy autolocalizingImageNamed:name]. 303 | */ 304 | + (DPImage *_Nullable)autolocalizingImageNamed:(NSString *)name; 305 | 306 | @end 307 | 308 | NS_ASSUME_NONNULL_END 309 | -------------------------------------------------------------------------------- /DPLocalization/include/Plural+DPLocalization.h: -------------------------------------------------------------------------------- 1 | // 2 | // Plural+DPLocalization.h 3 | // DP Commons 4 | // 5 | // Created by Dmitriy Petrusevich on 25/06/15. 6 | // Copyright (c) 2015 Dmitriy Petrusevich. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "DPFormattedValue.h" 11 | #import "dp_gen_plural.h" 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | NSString *dp_key_from_pluralrule(enum DPPluralRule rule); 16 | dp_plural_rules_func dp_plural_rules_for_lang_code(NSString *lang_code); 17 | 18 | 19 | @interface NSNumber (DPLocalization_Plural) 20 | - (enum DPPluralRule)pluralRuleWithLanguage:(NSString *)language; 21 | - (enum DPPluralRule)pluralRuleWithRules:(dp_plural_rules_func)rules; 22 | @end 23 | 24 | @interface DPFormattedValue (DPLocalization_Plural) 25 | - (enum DPPluralRule)pluralRuleWithLanguage:(NSString *)language; 26 | - (enum DPPluralRule)pluralRuleWithRules:(dp_plural_rules_func)rules; 27 | @end 28 | 29 | NS_ASSUME_NONNULL_END 30 | -------------------------------------------------------------------------------- /Example/CustomBundle/de.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | LocalizationDemo 4 | 5 | Created by dmitriy.petrusevich on 13.11.13. 6 | Copyright (c) 2013 Dmitriy Petrusevich. All rights reserved. 7 | */ 8 | 9 | "TITLE" = "German B2"; 10 | "LABEL_TEXT" = "German - %3$@\n%2$@\n{%1$@}"; 11 | "TEXTVIEW_TEXT" = "German\n\nUITextView text.\n\nSee user defined runtime attributes in storyboard."; 12 | 13 | 14 | "FILES_REMAINING_T" = "FILES_REMAINING_FORMAT"; 15 | 16 | "TESTS_STRING" = "de b2"; 17 | "TESTS_STRING_ARGS_1" = "de %@ %@ %@"; 18 | "TESTS_STRING_ARGS_2" = "de %3$@ %1$@ %2$@"; 19 | "TESTS_STRING_ARGS_3" = "de %1$@ %1$@ %1$@"; 20 | "TESTS_STRING_ARGS_4" = "de %2$@ %3$@ %4$@"; -------------------------------------------------------------------------------- /Example/CustomBundle/de.lproj/Localizable1.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | LocalizationDemo 4 | 5 | Created by dmitriy.petrusevich on 13.11.13. 6 | Copyright (c) 2013 Dmitriy Petrusevich. All rights reserved. 7 | */ 8 | 9 | "TITLE" = "German 11 B2"; 10 | "LABEL_TEXT" = "German - %3$@\n%2$@\n%1$@ 11"; 11 | "TEXTVIEW_TEXT" = "German\n\nUITextView text.\n\nSee user defined runtime attributes in storyboard. 11"; 12 | 13 | 14 | "TESTS_STRING" = "de 1 b2"; -------------------------------------------------------------------------------- /Example/CustomBundle/de.lproj/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullic/DPLocalizationManager/b955ab6c787dee7ef875cc11b523254f4592efbe/Example/CustomBundle/de.lproj/image.png -------------------------------------------------------------------------------- /Example/CustomBundle/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | LocalizationDemo 4 | 5 | Created by dmitriy.petrusevich on 13.11.13. 6 | Copyright (c) 2013 Dmitriy Petrusevich. All rights reserved. 7 | */ 8 | 9 | "TITLE" = "English B2"; 10 | "LABEL_TEXT" = "English - {%1$@}\n%2$@\n{%3$@}"; 11 | "TEXTVIEW_TEXT" = "English\n\nUITextView text. {link to DPLocalizationManager} \n\nSee user defined runtime attributes in storyboard."; 12 | 13 | 14 | "FILES_REMAINING_T" = "FILES_REMAINING_FORMAT"; 15 | 16 | "TESTS_STRING" = "en b2"; 17 | "TESTS_STRING_ARGS_1" = "en %@ %@ %@"; 18 | "TESTS_STRING_ARGS_2" = "en %3$@ %1$@ %2$@"; 19 | "TESTS_STRING_ARGS_3" = "en %1$@ %1$@ %1$@"; 20 | "TESTS_STRING_ARGS_4" = "en %2$@ %3$@ %4$@"; 21 | -------------------------------------------------------------------------------- /Example/CustomBundle/en.lproj/Localizable.stringsdict: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | FILES_REMAINING_FORMAT 6 | 7 | NSStringLocalizedFormatKey 8 | %#@files@ 9 | 10 | files 11 | 12 | NSStringFormatSpecTypeKey 13 | NSStringPluralRuleType 14 | NSStringFormatValueTypeKey 15 | @ 16 | one 17 | %@ file remaining (one) 18 | other 19 | %@ files remaining (other) 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Example/CustomBundle/en.lproj/Localizable1.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | LocalizationDemo 4 | 5 | Created by dmitriy.petrusevich on 13.11.13. 6 | Copyright (c) 2013 Dmitriy Petrusevich. All rights reserved. 7 | */ 8 | 9 | "TITLE" = "English 11 B2"; 10 | "LABEL_TEXT" = "English - %1$@\n%2$@\n%3$@ 11"; 11 | "TEXTVIEW_TEXT" = "English\n\nUITextView text.\n\nSee user defined runtime attributes in storyboard. 11"; 12 | 13 | 14 | "TESTS_STRING" = "en 1 b2"; -------------------------------------------------------------------------------- /Example/CustomBundle/en.lproj/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullic/DPLocalizationManager/b955ab6c787dee7ef875cc11b523254f4592efbe/Example/CustomBundle/en.lproj/image.png -------------------------------------------------------------------------------- /Example/CustomBundle/ru.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | LocalizationDemo 4 | 5 | Created by dmitriy.petrusevich on 13.11.13. 6 | Copyright (c) 2013 Dmitriy Petrusevich. All rights reserved. 7 | */ 8 | 9 | "TITLE" = "Russian B2"; 10 | "LABEL_TEXT" = "Russian - %3$@\n{%1$@}\n%2$@"; 11 | "TEXTVIEW_TEXT" = "Russian\n\nUITextView text.\n\nSee user defined runtime attributes in storyboard."; 12 | 13 | 14 | "FILES_REMAINING_T" = "FILES_REMAINING_FORMAT"; 15 | 16 | "TESTS_STRING" = "ru b2"; 17 | "TESTS_STRING_ARGS_1" = "ru %@ %@ %@"; 18 | "TESTS_STRING_ARGS_2" = "ru %3$@ %1$@ %2$@"; 19 | "TESTS_STRING_ARGS_3" = "ru %1$@ %1$@ %1$@"; 20 | "TESTS_STRING_ARGS_4" = "ru %2$@ %3$@ %4$@"; -------------------------------------------------------------------------------- /Example/CustomBundle/ru.lproj/Localizable.stringsdict: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | FILES_REMAINING_FORMAT 6 | 7 | NSStringLocalizedFormatKey 8 | %#@files@ 9 | files 10 | 11 | NSStringFormatSpecTypeKey 12 | NSStringPluralRuleType 13 | NSStringFormatValueTypeKey 14 | @ 15 | one 16 | Остался %@ файл (one) 17 | few 18 | Осталось %@ файла (few) 19 | many 20 | Осталось %@ файлов (many) 21 | other 22 | Осталось %@ файла (other) 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/CustomBundle/ru.lproj/Localizable1.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | LocalizationDemo 4 | 5 | Created by dmitriy.petrusevich on 13.11.13. 6 | Copyright (c) 2013 Dmitriy Petrusevich. All rights reserved. 7 | */ 8 | 9 | "TITLE" = "Russian 11 B2"; 10 | "LABEL_TEXT" = "Russian - %3$@\n%1$@\n%2$@ 11"; 11 | "TEXTVIEW_TEXT" = "Russian\n\nUITextView text.\n\nSee user defined runtime attributes in storyboard. 11"; 12 | 13 | 14 | "TESTS_STRING" = "ru 1 b2"; -------------------------------------------------------------------------------- /Example/CustomBundle/ru.lproj/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullic/DPLocalizationManager/b955ab6c787dee7ef875cc11b523254f4592efbe/Example/CustomBundle/ru.lproj/image.png -------------------------------------------------------------------------------- /Example/DPLocalization-OSX/DPLocalization-OSX.h: -------------------------------------------------------------------------------- 1 | // 2 | // DPLocalization-OSX.h 3 | // DPLocalization-OSX 4 | // 5 | // Created by Dmitriy Petrusevich on 24/05/2017. 6 | // Copyright © 2017 Dmitriy Petrusevich. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for DPLocalization-OSX. 12 | FOUNDATION_EXPORT double DPLocalization_OSXVersionNumber; 13 | 14 | //! Project version string for DPLocalization-OSX. 15 | FOUNDATION_EXPORT const unsigned char DPLocalization_OSXVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Example/DPLocalization-OSX/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSHumanReadableCopyright 22 | Copyright © 2017 Dmitriy Petrusevich. All rights reserved. 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/DPLocalization-iOS/DPLocalizationVer.h: -------------------------------------------------------------------------------- 1 | // 2 | // DPLocalization.h 3 | // DPLocalization 4 | // 5 | // Created by Dmitriy Petrusevich on 24/05/2017. 6 | // Copyright © 2017 Dmitriy Petrusevich. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for DPLocalization. 12 | FOUNDATION_EXPORT double DPLocalizationVersionNumber; 13 | 14 | //! Project version string for DPLocalization. 15 | FOUNDATION_EXPORT const unsigned char DPLocalizationVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Example/DPLocalization-iOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Example/LocalizationDemo-osx/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // LocalizationDemo-osx 4 | // 5 | // Created by Dmitriy Petrusevich on 23/02/15. 6 | // Copyright (c) 2015 Dmitriy Petrusevich. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : NSObject 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /Example/LocalizationDemo-osx/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // LocalizationDemo-osx 4 | // 5 | // Created by Dmitriy Petrusevich on 23/02/15. 6 | // Copyright (c) 2015 Dmitriy Petrusevich. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import 11 | 12 | 13 | @interface AppDelegate () 14 | @property (weak) IBOutlet NSWindow *window; 15 | @property (weak) IBOutlet NSSegmentedControl *langSelector; 16 | @property (weak) IBOutlet NSSegmentedControl *bundleSelector; 17 | @property (weak) IBOutlet NSTextField *label; 18 | @end 19 | 20 | @implementation AppDelegate 21 | 22 | - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { 23 | NSLog(@"Preffered language: %@", [DPLocalizationManager preferredLanguage]); 24 | NSLog(@"Selected language: %@", dp_get_current_language()); 25 | NSLog(@"Supported language: %@", [DPLocalizationManager supportedLanguages]); 26 | 27 | [self updateLangSelector]; 28 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(languageDidChangeNotification:) name:DPLanguageDidChangeNotification object:nil]; 29 | 30 | [self.label updateAutolocalizationArguments:@[@"Hello", @1234567890, [NSDate date]]]; 31 | } 32 | 33 | - (void)applicationWillTerminate:(NSNotification *)aNotification { 34 | // Insert code here to tear down your application 35 | } 36 | 37 | - (IBAction)languageDidChange:(id)sender { 38 | switch (self.langSelector.selectedSegment) { 39 | case 0: 40 | dp_set_current_language(@"en"); 41 | break; 42 | case 1: 43 | dp_set_current_language(@"ru"); 44 | break; 45 | case 2: 46 | dp_set_current_language(@"de"); 47 | break; 48 | default: 49 | dp_set_current_language(nil); 50 | break; 51 | } 52 | } 53 | 54 | - (void)languageDidChangeNotification:(NSNotification *)notification { 55 | [self updateLangSelector]; 56 | } 57 | 58 | - (void)updateLangSelector { 59 | if ([dp_get_current_language() isEqualToString:@"en"]) { 60 | self.langSelector.selectedSegment = 0; 61 | } 62 | 63 | if ([dp_get_current_language() isEqualToString:@"ru"]) { 64 | self.langSelector.selectedSegment = 1; 65 | } 66 | 67 | if ([dp_get_current_language() isEqualToString:@"de"]) { 68 | self.langSelector.selectedSegment = 2; 69 | } 70 | 71 | if (dp_get_current_language() == nil) { 72 | self.langSelector.selectedSegment = 3; 73 | } 74 | } 75 | 76 | - (IBAction)bundleDidChange:(id)sender { 77 | switch (self.bundleSelector.selectedSegment) { 78 | case 0: 79 | [DPLocalizationManager currentManager].defaultBundle = nil; 80 | break; 81 | 82 | case 1: { 83 | NSString *bundlePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"CustomBundle"]; 84 | [DPLocalizationManager currentManager].defaultBundle = [[NSBundle alloc] initWithPath:bundlePath]; 85 | break; 86 | } 87 | 88 | default: 89 | break; 90 | } 91 | 92 | NSLog(@"Preffered language: %@", [[DPLocalizationManager currentManager].defaultBundle preferredLanguage]); 93 | NSLog(@"Selected language: %@", dp_get_current_language()); 94 | NSLog(@"Supported language: %@", [[DPLocalizationManager currentManager].defaultBundle localizations]); 95 | } 96 | 97 | @end 98 | -------------------------------------------------------------------------------- /Example/LocalizationDemo-osx/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /Example/LocalizationDemo-osx/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | NSHumanReadableCopyright 28 | Copyright © 2015 Dmitriy Petrusevich. All rights reserved. 29 | NSMainNibFile 30 | MainMenu 31 | NSPrincipalClass 32 | NSApplication 33 | 34 | 35 | -------------------------------------------------------------------------------- /Example/LocalizationDemo-osx/de.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | LocalizationDemo 4 | 5 | Created by Dmitriy Petrusevich on 23/02/15. 6 | Copyright (c) 2015 Dmitriy Petrusevich. All rights reserved. 7 | */ 8 | 9 | "WND_TITLE" = "German"; 10 | "LABEL_TEXT" = "German - %3$@\n%2$@\n%1$@"; 11 | "PLACEHOLDER_TEXT" = "Placeholder (de)"; 12 | 13 | "MENU_FILE" = "Datei"; 14 | "CHECK_BOX" = "check (de)"; 15 | "TEXTVIEW" = "Text view (de)"; 16 | 17 | "ITEM_1" = "Item 1 (de)"; 18 | "ITEM_2" = "Item 2 (de)"; 19 | "ITEM_3" = "Item 3 (de)"; -------------------------------------------------------------------------------- /Example/LocalizationDemo-osx/de.lproj/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullic/DPLocalizationManager/b955ab6c787dee7ef875cc11b523254f4592efbe/Example/LocalizationDemo-osx/de.lproj/image.png -------------------------------------------------------------------------------- /Example/LocalizationDemo-osx/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | LocalizationDemo 4 | 5 | Created by Dmitriy Petrusevich on 23/02/15. 6 | Copyright (c) 2015 Dmitriy Petrusevich. All rights reserved. 7 | */ 8 | 9 | "WND_TITLE" = "English"; 10 | "LABEL_TEXT" = "English - {%1$@}\n%2$@\n{%3$@}"; 11 | "PLACEHOLDER_TEXT" = "Placeholder (en)"; 12 | 13 | "MENU_FILE" = "File"; 14 | "CHECK_BOX" = "check (en)"; 15 | "TEXTVIEW" = "Text view (en)"; 16 | 17 | "ITEM_1" = "Item 1 (en)"; 18 | "ITEM_2" = "Item 2 (en)"; 19 | "ITEM_3" = "Item 3 (en)"; -------------------------------------------------------------------------------- /Example/LocalizationDemo-osx/en.lproj/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullic/DPLocalizationManager/b955ab6c787dee7ef875cc11b523254f4592efbe/Example/LocalizationDemo-osx/en.lproj/image.png -------------------------------------------------------------------------------- /Example/LocalizationDemo-osx/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // LocalizationDemo-osx 4 | // 5 | // Created by Dmitriy Petrusevich on 23/02/15. 6 | // Copyright (c) 2015 Dmitriy Petrusevich. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | int main(int argc, const char * argv[]) { 12 | return NSApplicationMain(argc, argv); 13 | } 14 | -------------------------------------------------------------------------------- /Example/LocalizationDemo-osx/ru.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | LocalizationDemo 4 | 5 | Created by Dmitriy Petrusevich on 23/02/15. 6 | Copyright (c) 2015 Dmitriy Petrusevich. All rights reserved. 7 | */ 8 | 9 | "WND_TITLE" = "Russian"; 10 | "LABEL_TEXT" = "Russian - %3$@\n%1$@\n%2$@"; 11 | "PLACEHOLDER_TEXT" = "Placeholder (ru)"; 12 | 13 | "MENU_FILE" = "Файл"; 14 | "CHECK_BOX" = "check (ru)"; 15 | "TEXTVIEW" = "Text view (ru)"; 16 | 17 | "ITEM_1" = "Item 1 (ru)"; 18 | "ITEM_2" = "Item 2 (ru)"; 19 | "ITEM_3" = "Item 3 (ru)"; -------------------------------------------------------------------------------- /Example/LocalizationDemo-osx/ru.lproj/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullic/DPLocalizationManager/b955ab6c787dee7ef875cc11b523254f4592efbe/Example/LocalizationDemo-osx/ru.lproj/image.png -------------------------------------------------------------------------------- /Example/LocalizationDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/LocalizationDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/LocalizationDemo.xcodeproj/xcshareddata/xcbaselines/B294056B1AA439180049BF70.xcbaseline/C26C7201-8BA0-43BA-A803-AD7C23969A4E.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | classNames 6 | 7 | BasicNSObjectTests 8 | 9 | testLocalizationPerformance 10 | 11 | com.apple.XCTPerformanceMetric_WallClockTime 12 | 13 | baselineAverage 14 | 1.15 15 | baselineIntegrationDisplayName 16 | Local Baseline 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Example/LocalizationDemo.xcodeproj/xcshareddata/xcbaselines/B294056B1AA439180049BF70.xcbaseline/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | runDestinationsByUUID 6 | 7 | C26C7201-8BA0-43BA-A803-AD7C23969A4E 8 | 9 | localComputer 10 | 11 | busSpeedInMHz 12 | 100 13 | cpuCount 14 | 1 15 | cpuKind 16 | Intel Core i7 17 | cpuSpeedInMHz 18 | 2300 19 | logicalCPUCoresPerPackage 20 | 8 21 | modelCode 22 | Macmini6,2 23 | physicalCPUCoresPerPackage 24 | 4 25 | platformIdentifier 26 | com.apple.platform.macosx 27 | 28 | targetArchitecture 29 | i386 30 | targetDevice 31 | 32 | modelCode 33 | iPhone5,1 34 | platformIdentifier 35 | com.apple.platform.iphonesimulator 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /Example/LocalizationDemo.xcodeproj/xcshareddata/xcschemes/DPLocalization-OSX.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 35 | 36 | 47 | 48 | 54 | 55 | 56 | 57 | 58 | 59 | 65 | 66 | 72 | 73 | 74 | 75 | 77 | 78 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /Example/LocalizationDemo.xcodeproj/xcshareddata/xcschemes/DPLocalization-iOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 54 | 60 | 61 | 62 | 63 | 69 | 70 | 76 | 77 | 78 | 79 | 81 | 82 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /Example/LocalizationDemo.xcodeproj/xcshareddata/xcschemes/LocalizationDemo-osx.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /Example/LocalizationDemo.xcodeproj/xcshareddata/xcschemes/LocalizationDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 67 | 68 | 78 | 80 | 86 | 87 | 88 | 89 | 90 | 91 | 97 | 99 | 105 | 106 | 107 | 108 | 110 | 111 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /Example/LocalizationDemo.xcodeproj/xcshareddata/xcschemes/LocalizationDemoSwift.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /Example/LocalizationDemo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // LocalizationDemo 4 | // 5 | // Created by dmitriy.petrusevich on 13.11.13. 6 | // Copyright (c) 2013 Dmitriy Petrusevich. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Example/LocalizationDemo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // LocalizationDemo 4 | // 5 | // Created by dmitriy.petrusevich on 13.11.13. 6 | // Copyright (c) 2013 Dmitriy Petrusevich. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @implementation AppDelegate 12 | 13 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 14 | { 15 | NSLog(@"%@", [[NSBundle mainBundle] bundlePath]); 16 | 17 | #if TESTING 18 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 19 | self.window.rootViewController = [[UIViewController alloc] init]; 20 | [self.window makeKeyAndVisible]; 21 | #endif 22 | // Override point for customization after application launch. 23 | return YES; 24 | } 25 | 26 | - (void)applicationWillResignActive:(UIApplication *)application 27 | { 28 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 29 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 30 | } 31 | 32 | - (void)applicationDidEnterBackground:(UIApplication *)application 33 | { 34 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 35 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 36 | } 37 | 38 | - (void)applicationWillEnterForeground:(UIApplication *)application 39 | { 40 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 41 | } 42 | 43 | - (void)applicationDidBecomeActive:(UIApplication *)application 44 | { 45 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 46 | } 47 | 48 | - (void)applicationWillTerminate:(UIApplication *)application 49 | { 50 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 51 | } 52 | 53 | @end 54 | -------------------------------------------------------------------------------- /Example/LocalizationDemo/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ios-marketing", 45 | "size" : "1024x1024", 46 | "scale" : "1x" 47 | } 48 | ], 49 | "info" : { 50 | "version" : 1, 51 | "author" : "xcode" 52 | } 53 | } -------------------------------------------------------------------------------- /Example/LocalizationDemo/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "7.0", 8 | "scale" : "2x" 9 | }, 10 | { 11 | "orientation" : "portrait", 12 | "idiom" : "iphone", 13 | "subtype" : "retina4", 14 | "extent" : "full-screen", 15 | "minimum-system-version" : "7.0", 16 | "scale" : "2x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Example/LocalizationDemo/Launch Screen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 28 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /Example/LocalizationDemo/LocalizationDemo-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | Launch Screen 29 | UIMainStoryboardFile 30 | Main 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /Example/LocalizationDemo/LocalizationDemo-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #import 8 | 9 | #ifndef __IPHONE_5_0 10 | #warning "This project uses features only available in iOS SDK 5.0 and later." 11 | #endif 12 | 13 | #ifdef __OBJC__ 14 | #import 15 | #import 16 | 17 | #import 18 | #endif 19 | -------------------------------------------------------------------------------- /Example/LocalizationDemo/SecondViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SecondViewController.h 3 | // LocalizationDemo 4 | // 5 | // Created by Dmitriy Petrusevich on 22/06/15. 6 | // Copyright (c) 2015 Dmitriy Petrusevich. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SecondViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Example/LocalizationDemo/SecondViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SecondViewController.m 3 | // LocalizationDemo 4 | // 5 | // Created by Dmitriy Petrusevich on 22/06/15. 6 | // Copyright (c) 2015 Dmitriy Petrusevich. All rights reserved. 7 | // 8 | 9 | #import "SecondViewController.h" 10 | 11 | @interface SecondViewController () 12 | @property (nonatomic, weak) IBOutlet UILabel *countLabel; 13 | @property (nonatomic, weak) IBOutlet UIStepper *stepper; 14 | @property (nonatomic, weak) IBOutlet UISegmentedControl *bundleSelector; 15 | @property (nonatomic, strong) NSNumberFormatter *numberFormatter; 16 | @end 17 | 18 | @implementation SecondViewController 19 | 20 | - (void)viewDidLoad { 21 | [super viewDidLoad]; 22 | 23 | self.numberFormatter = [[NSNumberFormatter alloc] init]; 24 | self.numberFormatter.numberStyle = NSNumberFormatterDecimalStyle; 25 | 26 | DPFormattedValue *numberValue = [DPFormattedValue formattedValueWithValue:@(self.stepper.value) formatter:self.numberFormatter]; 27 | [self.countLabel updateAutolocalizationArguments:@[numberValue]]; 28 | 29 | 30 | if ([[DPLocalizationManager currentManager].defaultBundle isEqual:[NSBundle mainBundle]] == YES) { 31 | self.bundleSelector.selectedSegmentIndex = 0; 32 | } 33 | else if ([[DPLocalizationManager currentManager].defaultBundle isKindOfClass:[DPLocalizationBundle class]] == YES) { 34 | self.bundleSelector.selectedSegmentIndex = 2; 35 | } 36 | else { 37 | self.bundleSelector.selectedSegmentIndex = 1; 38 | } 39 | } 40 | 41 | - (IBAction)stepperValueChanged:(id)sender { 42 | DPFormattedValue *numberValue = [DPFormattedValue formattedValueWithValue:@(self.stepper.value) formatter:self.numberFormatter]; 43 | [self.countLabel updateAutolocalizationArguments:@[numberValue]]; 44 | } 45 | 46 | - (IBAction)bundleDidChange:(id)sender { 47 | switch (self.bundleSelector.selectedSegmentIndex) { 48 | case 0: 49 | [DPLocalizationManager currentManager].defaultBundle = nil; 50 | break; 51 | 52 | case 1: { 53 | NSString *bundlePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"CustomBundle"]; 54 | [DPLocalizationManager currentManager].defaultBundle = [[NSBundle alloc] initWithPath:bundlePath]; 55 | break; 56 | } 57 | 58 | case 2: { 59 | DPLocalizationBundle *bundle = [DPLocalizationBundle defaultBundle]; 60 | NSDictionary *table = [bundle stringsTableWithName:nil language:@"en"]; 61 | 62 | NSError *error = nil; 63 | table = @{ 64 | @"TITLE": @"Runtime", 65 | @"LABEL_TEXT": @"Runtime - {%1$@}\n%2$@\n{%3$@}", 66 | @"TEXTVIEW_TEXT": @"Runtime\n\nUITextView text. {link to DPLocalizationManager} ", 67 | }; 68 | [bundle setStringsTable:table withName:nil language:@"en" error:&error]; 69 | 70 | table = @{ 71 | @"TITLE": @"Русский", 72 | @"LABEL_TEXT": @"Русский - %1$@\n%2$@\n%3$@", 73 | @"TEXTVIEW_TEXT": @"Русский\n\n{смотри сюда} ", 74 | }; 75 | [bundle setStringsTable:table withName:nil language:@"ru" error:&error]; 76 | 77 | [DPLocalizationManager currentManager].defaultBundle = bundle; 78 | } 79 | 80 | default: 81 | break; 82 | } 83 | 84 | NSLog(@"Preffered language: %@", [[DPLocalizationManager currentManager].defaultBundle preferredLanguage]); 85 | NSLog(@"Selected language: %@", dp_get_current_language()); 86 | NSLog(@"Supported language: %@", [[DPLocalizationManager currentManager].defaultBundle localizations]); 87 | } 88 | 89 | @end 90 | -------------------------------------------------------------------------------- /Example/LocalizationDemo/Settings.bundle/Root.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | Type 9 | PSMultiValueSpecifier 10 | Title 11 | Language 12 | Key 13 | DPLanguageKey 14 | DefaultValue 15 | 16 | Values 17 | 18 | 19 | en 20 | ru 21 | de 22 | 23 | Titles 24 | 25 | Auto 26 | English 27 | Russin 28 | German 29 | 30 | 31 | 32 | StringsTable 33 | Root 34 | 35 | 36 | -------------------------------------------------------------------------------- /Example/LocalizationDemo/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // LocalizationDemo 4 | // 5 | // Created by dmitriy.petrusevich on 13.11.13. 6 | // Copyright (c) 2013 Dmitriy Petrusevich. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Example/LocalizationDemo/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // LocalizationDemo 4 | // 5 | // Created by dmitriy.petrusevich on 13.11.13. 6 | // Copyright (c) 2013 Dmitriy Petrusevich. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | 11 | @interface ViewController () 12 | @property (nonatomic, weak) IBOutlet UILabel *label; 13 | @property (nonatomic, weak) IBOutlet UILabel *startup; 14 | @property (nonatomic, weak) IBOutlet UISegmentedControl *langSelector; 15 | @property (nonatomic, weak) IBOutlet UISegmentedControl *fileSelector; 16 | @property (nonatomic, weak) IBOutlet UIImageView *imageView; 17 | @end 18 | 19 | @implementation ViewController 20 | 21 | - (void)viewDidLoad 22 | { 23 | [super viewDidLoad]; 24 | 25 | // NSString *alStr = [NSString autolocalizingStringWithLocalizationKey:@"TITLE"]; 26 | // NSString *alPath = [[NSBundle mainBundle] autolocalizingPathForResource:@"Localizable" ofType:@"strings"]; 27 | // 28 | // dp_set_current_language(@"en"); 29 | // CFShow((__bridge CFTypeRef)(alStr)); 30 | // CFShow((__bridge CFTypeRef)(alPath)); 31 | // 32 | // dp_set_current_language(@"ru"); 33 | // CFShow((__bridge CFTypeRef)(alStr)); 34 | // CFShow((__bridge CFTypeRef)(alPath)); 35 | // 36 | // dp_set_current_language(@"de"); 37 | // CFShow((__bridge CFTypeRef)(alStr)); 38 | // CFShow((__bridge CFTypeRef)(alPath)); 39 | 40 | self.imageView.autolocalizationImageName = @"image"; 41 | 42 | 43 | NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; 44 | formatter.dateStyle = NSDateFormatterFullStyle; 45 | formatter.timeStyle = NSDateFormatterNoStyle; 46 | 47 | DPFormattedValue *dateValue = [DPFormattedValue formattedValueWithValue:[NSDate date] formatter:formatter]; 48 | self.startup.text = DPLocalizedString(@"TITLE", nil); 49 | self.label.autolocalizationKey = @"LABEL_TEXT"; 50 | [self.label updateAutolocalizationArguments:@[@"Hello", @12.34, dateValue]]; 51 | self.autolocalizationKey = @"TITLE"; 52 | 53 | NSLog(@"Preffered language: %@", [DPLocalizationManager preferredLanguage]); 54 | NSLog(@"Selected language: %@", dp_get_current_language()); 55 | NSLog(@"Supported language: %@", [DPLocalizationManager supportedLanguages]); 56 | 57 | [self updateLangSelector]; 58 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(languageDidChangeNotification:) name:DPLanguageDidChangeNotification object:nil]; 59 | } 60 | 61 | - (IBAction)languageChangeAction:(id)sender { 62 | switch (self.langSelector.selectedSegmentIndex) { 63 | case 0: 64 | dp_set_current_language(@"en"); 65 | break; 66 | case 1: 67 | dp_set_current_language(@"ru"); 68 | break; 69 | case 2: 70 | dp_set_current_language(@"de"); 71 | break; 72 | default: 73 | dp_set_current_language(nil); 74 | break; 75 | } 76 | } 77 | 78 | - (IBAction)localizationFileChangeName:(id)sender 79 | { 80 | switch (self.fileSelector.selectedSegmentIndex) { 81 | case 0: 82 | [DPLocalizationManager currentManager].defaultStringTableName = @"Localizable"; 83 | break; 84 | case 1: 85 | [DPLocalizationManager currentManager].defaultStringTableName = @"Localizable1"; 86 | break; 87 | default: 88 | [DPLocalizationManager currentManager].defaultStringTableName = nil; 89 | break; 90 | } 91 | } 92 | 93 | - (void)languageDidChangeNotification:(NSNotification *)notification { 94 | [self updateLangSelector]; 95 | } 96 | 97 | - (void)updateLangSelector { 98 | if ([dp_get_current_language() isEqualToString:@"en"]) { 99 | self.langSelector.selectedSegmentIndex = 0; 100 | } 101 | 102 | if ([dp_get_current_language() isEqualToString:@"ru"]) { 103 | self.langSelector.selectedSegmentIndex = 1; 104 | } 105 | 106 | if ([dp_get_current_language() isEqualToString:@"de"]) { 107 | self.langSelector.selectedSegmentIndex = 2; 108 | } 109 | 110 | if (dp_get_current_language() == nil) { 111 | self.langSelector.selectedSegmentIndex = 3; 112 | } 113 | } 114 | 115 | @end 116 | -------------------------------------------------------------------------------- /Example/LocalizationDemo/de.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | LocalizationDemo 4 | 5 | Created by dmitriy.petrusevich on 13.11.13. 6 | Copyright (c) 2013 Dmitriy Petrusevich. All rights reserved. 7 | */ 8 | 9 | "TITLE" = "German"; 10 | "LABEL_TEXT" = "German - %3$@\n%2$@\n{%1$@}"; 11 | "TEXTVIEW_TEXT" = "German\n\nUITextView text.\n\nSee user defined runtime attributes in storyboard."; 12 | 13 | 14 | "FILES_REMAINING_T" = "FILES_REMAINING_FORMAT"; 15 | 16 | "TESTS_STRING" = "de"; 17 | "TESTS_STRING_ARGS_1" = "de %@ %@ %@"; 18 | "TESTS_STRING_ARGS_2" = "de %3$@ %1$@ %2$@"; 19 | "TESTS_STRING_ARGS_3" = "de %1$@ %1$@ %1$@"; 20 | "TESTS_STRING_ARGS_4" = "de %2$@ %3$@ %4$@"; -------------------------------------------------------------------------------- /Example/LocalizationDemo/de.lproj/Localizable1.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | LocalizationDemo 4 | 5 | Created by dmitriy.petrusevich on 13.11.13. 6 | Copyright (c) 2013 Dmitriy Petrusevich. All rights reserved. 7 | */ 8 | 9 | "TITLE" = "German 11"; 10 | "LABEL_TEXT" = "German - %3$@\n%2$@\n%1$@ 11"; 11 | "TEXTVIEW_TEXT" = "German\n\nUITextView text.\n\nSee user defined runtime attributes in storyboard. 11"; 12 | 13 | 14 | "TESTS_STRING" = "de 1"; -------------------------------------------------------------------------------- /Example/LocalizationDemo/de.lproj/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullic/DPLocalizationManager/b955ab6c787dee7ef875cc11b523254f4592efbe/Example/LocalizationDemo/de.lproj/image.png -------------------------------------------------------------------------------- /Example/LocalizationDemo/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | LocalizationDemo 4 | 5 | Created by dmitriy.petrusevich on 13.11.13. 6 | Copyright (c) 2013 Dmitriy Petrusevich. All rights reserved. 7 | */ 8 | 9 | "TITLE" = "English"; 10 | "LABEL_TEXT" = "English - {%1$@}\n%2$@\n{%3$@}"; 11 | "TEXTVIEW_TEXT" = "English\n\nUITextView text. {link to DPLocalizationManager} \n\nSee user defined runtime attributes in storyboard."; 12 | 13 | "ALIGNMENT_EXAMPLE" = "{Centered text Example} \n Default Alignment \r\n {Centered text Example}"; 14 | 15 | "FILES_REMAINING_T" = "FILES_REMAINING_FORMAT"; 16 | 17 | "TESTS_STRING" = "en"; 18 | "TESTS_STRING_ARGS_1" = "en %@ %@ %@"; 19 | "TESTS_STRING_ARGS_2" = "en %3$@ %1$@ %2$@"; 20 | "TESTS_STRING_ARGS_3" = "en %1$@ %1$@ %1$@"; 21 | "TESTS_STRING_ARGS_4" = "en %2$@ %3$@ %4$@"; 22 | -------------------------------------------------------------------------------- /Example/LocalizationDemo/en.lproj/Localizable.stringsdict: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | FILES_REMAINING_FORMAT 6 | 7 | NSStringLocalizedFormatKey 8 | %#@files@ 9 | 10 | files 11 | 12 | NSStringFormatSpecTypeKey 13 | NSStringPluralRuleType 14 | NSStringFormatValueTypeKey 15 | @ 16 | one 17 | %@ file remaining (one) 18 | other 19 | %@ files remaining (other) 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Example/LocalizationDemo/en.lproj/Localizable1.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | LocalizationDemo 4 | 5 | Created by dmitriy.petrusevich on 13.11.13. 6 | Copyright (c) 2013 Dmitriy Petrusevich. All rights reserved. 7 | */ 8 | 9 | "TITLE" = "English 11"; 10 | "LABEL_TEXT" = "English - %1$@\n%2$@\n%3$@ 11"; 11 | "TEXTVIEW_TEXT" = "English\n\nUITextView text.\n\nSee user defined runtime attributes in storyboard. 11"; 12 | 13 | 14 | "TESTS_STRING" = "en 1"; -------------------------------------------------------------------------------- /Example/LocalizationDemo/en.lproj/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullic/DPLocalizationManager/b955ab6c787dee7ef875cc11b523254f4592efbe/Example/LocalizationDemo/en.lproj/image.png -------------------------------------------------------------------------------- /Example/LocalizationDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // LocalizationDemo 4 | // 5 | // Created by dmitriy.petrusevich on 13.11.13. 6 | // Copyright (c) 2013 Dmitriy Petrusevich. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "AppDelegate.h" 12 | 13 | int main(int argc, char * argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Example/LocalizationDemo/ru.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | LocalizationDemo 4 | 5 | Created by dmitriy.petrusevich on 13.11.13. 6 | Copyright (c) 2013 Dmitriy Petrusevich. All rights reserved. 7 | */ 8 | 9 | "TITLE" = "Russian"; 10 | "LABEL_TEXT" = "Russian - %3$@\n{%1$@}\n%2$@"; 11 | "TEXTVIEW_TEXT" = "Russian\n\nUITextView text.\n\nSee user defined runtime attributes in storyboard."; 12 | 13 | 14 | "FILES_REMAINING_T" = "FILES_REMAINING_FORMAT"; 15 | 16 | "TESTS_STRING" = "ru"; 17 | "TESTS_STRING_ARGS_1" = "ru %@ %@ %@"; 18 | "TESTS_STRING_ARGS_2" = "ru %3$@ %1$@ %2$@"; 19 | "TESTS_STRING_ARGS_3" = "ru %1$@ %1$@ %1$@"; 20 | "TESTS_STRING_ARGS_4" = "ru %2$@ %3$@ %4$@"; -------------------------------------------------------------------------------- /Example/LocalizationDemo/ru.lproj/Localizable.stringsdict: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | FILES_REMAINING_FORMAT 6 | 7 | NSStringLocalizedFormatKey 8 | %#@files@ 9 | files 10 | 11 | NSStringFormatSpecTypeKey 12 | NSStringPluralRuleType 13 | NSStringFormatValueTypeKey 14 | @ 15 | one 16 | Остался %@ файл (one) 17 | few 18 | Осталось %@ файла (few) 19 | many 20 | Осталось %@ файлов (many) 21 | other 22 | Осталось %@ файла (other) 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/LocalizationDemo/ru.lproj/Localizable1.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | LocalizationDemo 4 | 5 | Created by dmitriy.petrusevich on 13.11.13. 6 | Copyright (c) 2013 Dmitriy Petrusevich. All rights reserved. 7 | */ 8 | 9 | "TITLE" = "Russian 11"; 10 | "LABEL_TEXT" = "Russian - %3$@\n%1$@\n%2$@ 11"; 11 | "TEXTVIEW_TEXT" = "Russian\n\nUITextView text.\n\nSee user defined runtime attributes in storyboard. 11"; 12 | 13 | 14 | "TESTS_STRING" = "ru 1"; -------------------------------------------------------------------------------- /Example/LocalizationDemo/ru.lproj/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullic/DPLocalizationManager/b955ab6c787dee7ef875cc11b523254f4592efbe/Example/LocalizationDemo/ru.lproj/image.png -------------------------------------------------------------------------------- /Example/LocalizationDemoSwift/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // LocalizationDemoSwift 4 | // 5 | // Created by Dmitriy Petrusevich on 24/05/2017. 6 | // Copyright © 2017 Dmitriy Petrusevich. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /Example/LocalizationDemoSwift/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Example/LocalizationDemoSwift/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // LocalizationDemoSwift 4 | // 5 | // Created by Dmitriy Petrusevich on 24/05/2017. 6 | // Copyright © 2017 Dmitriy Petrusevich. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import DPLocalization 11 | 12 | class ViewController: UIViewController { 13 | @IBOutlet weak var label: UILabel? 14 | @IBOutlet weak var startup: UILabel? 15 | @IBOutlet weak var langSelector: UISegmentedControl? 16 | @IBOutlet weak var fileSelector: UISegmentedControl? 17 | @IBOutlet weak var imageView: UIImageView? 18 | 19 | override func viewDidLoad() { 20 | super.viewDidLoad() 21 | 22 | self.imageView?.autolocalizationImageName = "image" 23 | 24 | let formatter = DateFormatter() 25 | formatter.dateStyle = .full 26 | formatter.timeStyle = .none 27 | 28 | let dateValue = DPFormattedValue(value: NSDate(), formatter: formatter)! 29 | 30 | self.startup?.text = DPLocalizedString("TITLE", nil) 31 | self.label?.autolocalizationKey = "LABEL_TEXT" 32 | self.label?.updateAutolocalizationArguments(["Hello", 12.34, dateValue]) 33 | self.autolocalizationKey = "TITLE" 34 | 35 | print("Preffered language:", DPLocalizationManager.preferredLanguage()) 36 | print("Selected language:", dp_get_current_language()) 37 | print("Supported language:", DPLocalizationManager.supportedLanguages()) 38 | 39 | self.updateLangSelector() 40 | NotificationCenter.default.addObserver(forName: NSNotification.Name.DPLanguageDidChange, object: nil, queue: OperationQueue.main) { (_ :Notification) in 41 | self.updateLangSelector() 42 | } 43 | } 44 | 45 | @IBAction func languageChangeAction(_ sender: Any?) { 46 | switch (self.langSelector?.selectedSegmentIndex ?? 0) { 47 | case 0: 48 | dp_set_current_language("en") 49 | case 1: 50 | dp_set_current_language("ru") 51 | case 2: 52 | dp_set_current_language("de") 53 | default: 54 | dp_set_current_language(nil) 55 | } 56 | } 57 | 58 | @IBAction func localizationFileChangeName(_ sender: Any?) { 59 | switch (self.fileSelector?.selectedSegmentIndex ?? 0) { 60 | case 0: 61 | DPLocalizationManager.current().defaultStringTableName = "Localizable" 62 | case 1: 63 | DPLocalizationManager.current().defaultStringTableName = "Localizable1" 64 | default: 65 | DPLocalizationManager.current().defaultStringTableName = nil 66 | } 67 | } 68 | 69 | func updateLangSelector() { 70 | if dp_get_current_language() == "en" { 71 | self.langSelector?.selectedSegmentIndex = 0; 72 | } 73 | else if dp_get_current_language() == "ru" { 74 | self.langSelector?.selectedSegmentIndex = 1; 75 | } 76 | else if dp_get_current_language() == "de" { 77 | self.langSelector?.selectedSegmentIndex = 2; 78 | } 79 | else if dp_get_current_language() == nil { 80 | self.langSelector?.selectedSegmentIndex = 3; 81 | } 82 | } 83 | } 84 | 85 | -------------------------------------------------------------------------------- /Example/Tests/BasicManagerTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // BasicManagerTests.m 3 | // LocalizationDemo 4 | // 5 | // Created by Dmitriy Petrusevich on 02/03/15. 6 | // Copyright (c) 2015 Dmitriy Petrusevich. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "DPLocalization.h" 12 | 13 | 14 | @interface BasicManagerTests : XCTestCase 15 | @end 16 | 17 | @implementation BasicManagerTests 18 | 19 | - (void)setUp { 20 | [super setUp]; 21 | [[DPLocalizationManager currentManager] setDefaultStringTableName:nil]; 22 | [[DPLocalizationManager currentManager] setCurrentLanguage:nil]; 23 | } 24 | 25 | - (void)testSupportedAndPreferredLanguages { 26 | NSArray *supportedLanguages = [DPLocalizationManager supportedLanguages]; 27 | XCTAssertNotNil(supportedLanguages); 28 | 29 | XCTAssertTrue([supportedLanguages containsObject:@"en"]); 30 | XCTAssertTrue([supportedLanguages containsObject:@"ru"]); 31 | XCTAssertTrue([supportedLanguages containsObject:@"de"]); 32 | 33 | NSString *preferredLanguage = [DPLocalizationManager preferredLanguage]; 34 | XCTAssertNotNil(preferredLanguage); 35 | 36 | NSString *localeLang = [[NSLocale preferredLanguages] firstObject]; 37 | if ([localeLang isEqualToString:@"en"]) { 38 | XCTAssertTrue([preferredLanguage isEqualToString:@"en"]); 39 | } 40 | else if ([localeLang isEqualToString:@"ru"]) { 41 | XCTAssertTrue([preferredLanguage isEqualToString:@"ru"]); 42 | } 43 | else if ([localeLang isEqualToString:@"de"]) { 44 | XCTAssertTrue([preferredLanguage isEqualToString:@"de"]); 45 | } 46 | } 47 | 48 | #pragma mark - Localization 49 | 50 | - (void)testLocalizedStringsTables { 51 | NSString *preferredLanguage = [DPLocalizationManager preferredLanguage]; 52 | XCTAssertNotNil(preferredLanguage); 53 | 54 | [DPLocalizationManager currentManager].defaultBundle = nil; 55 | XCTAssertEqualObjects([DPLocalizationManager currentManager].defaultBundle, [NSBundle mainBundle]); 56 | 57 | // "TESTS_STRING" - language code + " 1" 58 | 59 | [[DPLocalizationManager currentManager] setDefaultStringTableName:@"Localizable1"]; 60 | 61 | dp_set_current_language(nil); 62 | XCTAssertNil(dp_get_current_language()); 63 | XCTAssertTrue([DPLocalizedString(@"TESTS_STRING", nil) isEqualToString:[preferredLanguage stringByAppendingString:@" 1"]]); 64 | 65 | dp_set_current_language(@"en"); 66 | XCTAssertTrue([dp_get_current_language() isEqualToString:@"en"]); 67 | XCTAssertTrue([DPLocalizedString(@"TESTS_STRING", nil) isEqualToString:@"en 1"]); 68 | 69 | dp_set_current_language(@"ru"); 70 | XCTAssertTrue([dp_get_current_language() isEqualToString:@"ru"]); 71 | XCTAssertTrue([DPLocalizedString(@"TESTS_STRING", nil) isEqualToString:@"ru 1"]); 72 | 73 | dp_set_current_language(nil); 74 | XCTAssertNil(dp_get_current_language()); 75 | XCTAssertTrue([DPLocalizedString(@"TESTS_STRING", nil) isEqualToString:[preferredLanguage stringByAppendingString:@" 1"]]); 76 | 77 | dp_set_current_language(@"de"); 78 | XCTAssertTrue([dp_get_current_language() isEqualToString:@"de"]); 79 | XCTAssertTrue([DPLocalizedString(@"TESTS_STRING", nil) isEqualToString:@"de 1"]); 80 | 81 | // "TESTS_STRING" - language code 82 | 83 | [[DPLocalizationManager currentManager] setDefaultStringTableName:nil]; 84 | 85 | dp_set_current_language(nil); 86 | XCTAssertNil(dp_get_current_language()); 87 | XCTAssertTrue([DPLocalizedString(@"TESTS_STRING", nil) isEqualToString:preferredLanguage]); 88 | 89 | dp_set_current_language(@"en"); 90 | XCTAssertTrue([dp_get_current_language() isEqualToString:@"en"]); 91 | XCTAssertTrue([DPLocalizedString(@"TESTS_STRING", nil) isEqualToString:@"en"]); 92 | 93 | dp_set_current_language(@"ru"); 94 | XCTAssertTrue([dp_get_current_language() isEqualToString:@"ru"]); 95 | XCTAssertTrue([DPLocalizedString(@"TESTS_STRING", nil) isEqualToString:@"ru"]); 96 | 97 | dp_set_current_language(nil); 98 | XCTAssertNil(dp_get_current_language()); 99 | XCTAssertTrue([DPLocalizedString(@"TESTS_STRING", nil) isEqualToString:preferredLanguage]); 100 | 101 | dp_set_current_language(@"de"); 102 | XCTAssertTrue([dp_get_current_language() isEqualToString:@"de"]); 103 | XCTAssertTrue([DPLocalizedString(@"TESTS_STRING", nil) isEqualToString:@"de"]); 104 | } 105 | 106 | - (void)testLocalizedStringsBundles { 107 | NSString *preferredLanguage = [DPLocalizationManager preferredLanguage]; 108 | XCTAssertNotNil(preferredLanguage); 109 | 110 | NSString *bundlePath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"CustomBundle"]; 111 | [DPLocalizationManager currentManager].defaultBundle = [[NSBundle alloc] initWithPath:bundlePath]; 112 | XCTAssertEqualObjects(([DPLocalizationManager currentManager].defaultBundle.bundlePath), bundlePath); 113 | 114 | // "TESTS_STRING" - language code + " 1" 115 | 116 | [[DPLocalizationManager currentManager] setDefaultStringTableName:@"Localizable1"]; 117 | 118 | dp_set_current_language(nil); 119 | XCTAssertNil(dp_get_current_language()); 120 | XCTAssertTrue([DPLocalizedString(@"TESTS_STRING", nil) isEqualToString:[preferredLanguage stringByAppendingString:@" 1 b2"]]); 121 | 122 | dp_set_current_language(@"en"); 123 | XCTAssertTrue([dp_get_current_language() isEqualToString:@"en"]); 124 | XCTAssertTrue([DPLocalizedString(@"TESTS_STRING", nil) isEqualToString:@"en 1 b2"]); 125 | 126 | dp_set_current_language(@"ru"); 127 | XCTAssertTrue([dp_get_current_language() isEqualToString:@"ru"]); 128 | XCTAssertTrue([DPLocalizedString(@"TESTS_STRING", nil) isEqualToString:@"ru 1 b2"]); 129 | 130 | dp_set_current_language(nil); 131 | XCTAssertNil(dp_get_current_language()); 132 | XCTAssertTrue([DPLocalizedString(@"TESTS_STRING", nil) isEqualToString:[preferredLanguage stringByAppendingString:@" 1 b2"]]); 133 | 134 | dp_set_current_language(@"de"); 135 | XCTAssertTrue([dp_get_current_language() isEqualToString:@"de"]); 136 | XCTAssertTrue([DPLocalizedString(@"TESTS_STRING", nil) isEqualToString:@"de 1 b2"]); 137 | 138 | // "TESTS_STRING" - language code 139 | 140 | [[DPLocalizationManager currentManager] setDefaultStringTableName:nil]; 141 | 142 | dp_set_current_language(nil); 143 | XCTAssertNil(dp_get_current_language()); 144 | XCTAssertTrue([DPLocalizedString(@"TESTS_STRING", nil) isEqualToString:[preferredLanguage stringByAppendingString:@" b2"]]); 145 | 146 | dp_set_current_language(@"en"); 147 | XCTAssertTrue([dp_get_current_language() isEqualToString:@"en"]); 148 | XCTAssertTrue([DPLocalizedString(@"TESTS_STRING", nil) isEqualToString:@"en b2"]); 149 | 150 | dp_set_current_language(@"ru"); 151 | XCTAssertTrue([dp_get_current_language() isEqualToString:@"ru"]); 152 | XCTAssertTrue([DPLocalizedString(@"TESTS_STRING", nil) isEqualToString:@"ru b2"]); 153 | 154 | dp_set_current_language(nil); 155 | XCTAssertNil(dp_get_current_language()); 156 | XCTAssertTrue([DPLocalizedString(@"TESTS_STRING", nil) isEqualToString:[preferredLanguage stringByAppendingString:@" b2"]]); 157 | 158 | dp_set_current_language(@"de"); 159 | XCTAssertTrue([dp_get_current_language() isEqualToString:@"de"]); 160 | XCTAssertTrue([DPLocalizedString(@"TESTS_STRING", nil) isEqualToString:@"de b2"]); 161 | 162 | 163 | // Rollback defaultBundle and check changes 164 | [self testLocalizedStringsTables]; 165 | } 166 | 167 | - (void)testLocalizedPath { 168 | DPLocalizationManager *manager = [DPLocalizationManager currentManager]; 169 | NSString *preferredLocation = [[DPLocalizationManager preferredLanguage] stringByAppendingString:@".lproj"]; 170 | XCTAssertNotNil(preferredLocation); 171 | 172 | dp_set_current_language(nil); 173 | XCTAssertNil(dp_get_current_language()); 174 | XCTAssertNotEqual([[manager localizedPathForResource:@"image" ofType:@"png" bundle:nil] rangeOfString:preferredLocation].location, NSNotFound); 175 | 176 | dp_set_current_language(@"en"); 177 | XCTAssertTrue([dp_get_current_language() isEqualToString:@"en"]); 178 | XCTAssertNotEqual([[manager localizedPathForResource:@"image" ofType:@"png" bundle:nil] rangeOfString:@"en.lproj"].location, NSNotFound); 179 | 180 | dp_set_current_language(@"ru"); 181 | XCTAssertTrue([dp_get_current_language() isEqualToString:@"ru"]); 182 | XCTAssertNotEqual([[manager localizedPathForResource:@"image" ofType:@"png" bundle:nil] rangeOfString:@"ru.lproj"].location, NSNotFound); 183 | 184 | dp_set_current_language(nil); 185 | XCTAssertNil(dp_get_current_language()); 186 | XCTAssertNotEqual([[manager localizedPathForResource:@"image" ofType:@"png" bundle:nil] rangeOfString:preferredLocation].location, NSNotFound); 187 | 188 | dp_set_current_language(@"de"); 189 | XCTAssertTrue([dp_get_current_language() isEqualToString:@"de"]); 190 | XCTAssertNotEqual([[manager localizedPathForResource:@"image" ofType:@"png" bundle:nil] rangeOfString:@"de.lproj"].location, NSNotFound); 191 | } 192 | 193 | #pragma mark - Notifications 194 | 195 | - (void)testSetLanguagePostNotifications { 196 | XCTestExpectation *expectation = [self expectationWithDescription:@"Testing ... set new language \"Post Notification\""]; 197 | 198 | [[DPLocalizationManager currentManager] setCurrentLanguage:nil]; 199 | XCTAssertNil([[DPLocalizationManager currentManager] currentLanguage]); 200 | 201 | id observer = [[NSNotificationCenter defaultCenter] addObserverForName:DPLanguageDidChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { 202 | [expectation fulfill]; 203 | }]; 204 | 205 | 206 | NSString *newLng = @"en"; 207 | [[DPLocalizationManager currentManager] setCurrentLanguage:newLng]; 208 | 209 | [self waitForExpectationsWithTimeout:1.0 handler:^(NSError *error) { 210 | [[NSNotificationCenter defaultCenter] removeObserver:observer]; 211 | 212 | if (error) { 213 | XCTFail(@"Expectation Failed with error: %@", error); 214 | } 215 | else { 216 | XCTAssertTrue([[[DPLocalizationManager currentManager] currentLanguage] isEqualToString:newLng]); 217 | } 218 | }]; 219 | } 220 | 221 | - (void)testSetStringsTablePostNotifications { 222 | XCTestExpectation *expectation = [self expectationWithDescription:@"Testing ... set default string table name \"Post Notification\""]; 223 | 224 | [[DPLocalizationManager currentManager] setDefaultStringTableName:nil]; 225 | XCTAssertTrue([[[DPLocalizationManager currentManager] defaultStringTableName] isEqualToString:@"Localizable"]); 226 | 227 | id observer = [[NSNotificationCenter defaultCenter] addObserverForName:DPLanguageDidChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { 228 | [expectation fulfill]; 229 | }]; 230 | 231 | 232 | NSString *newTbl = @"Localizable 123"; 233 | [[DPLocalizationManager currentManager] setDefaultStringTableName:newTbl]; 234 | 235 | [self waitForExpectationsWithTimeout:1.0 handler:^(NSError *error) { 236 | [[NSNotificationCenter defaultCenter] removeObserver:observer]; 237 | 238 | if (error) { 239 | XCTFail(@"Expectation Failed with error: %@", error); 240 | } 241 | else { 242 | XCTAssertTrue([[[DPLocalizationManager currentManager] defaultStringTableName] isEqualToString:newTbl]); 243 | } 244 | }]; 245 | } 246 | 247 | @end 248 | -------------------------------------------------------------------------------- /Example/Tests/BasicNSObjectTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // BasicNSObjectTests.m 3 | // LocalizationDemo 4 | // 5 | // Created by Dmitriy Petrusevich on 02/03/15. 6 | // Copyright (c) 2015 Dmitriy Petrusevich. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "DPLocalization.h" 12 | #import "TestObject.h" 13 | 14 | 15 | @interface BasicNSObjectTests : XCTestCase 16 | @end 17 | 18 | @implementation BasicNSObjectTests 19 | 20 | - (void)setUp { 21 | [super setUp]; 22 | [[DPLocalizationManager currentManager] setDefaultStringTableName:nil]; 23 | [[DPLocalizationManager currentManager] setCurrentLanguage:nil]; 24 | } 25 | 26 | - (void)testBasicLocalization { 27 | XCTAssertTrue([[[DPLocalizationManager currentManager] defaultStringTableName] isEqualToString:@"Localizable"]); 28 | NSString *preferredLanguage = [DPLocalizationManager preferredLanguage]; 29 | XCTAssertNotNil(preferredLanguage); 30 | 31 | // "TESTS_STRING" - language code 32 | 33 | dp_set_current_language(@"en"); 34 | 35 | @autoreleasepool { 36 | TestObject *testObject = [[TestObject alloc] init]; 37 | [testObject setupAutolocalizationWithKey:@"TESTS_STRING" keyPath:@"testString"]; 38 | XCTAssertTrue([testObject isAutolocalizationEnabled]); 39 | XCTAssertTrue([testObject.testString isEqualToString:@"en"]); 40 | testObject = nil; 41 | } 42 | 43 | @autoreleasepool { 44 | TestObject *testObject = [[TestObject alloc] init]; 45 | [testObject setupAutolocalizationWithKey:@"TESTS_STRING" keyPath:@"testString"]; 46 | XCTAssertTrue([testObject isAutolocalizationEnabled]); 47 | XCTAssertTrue([testObject.testString isEqualToString:@"en"]); 48 | 49 | dp_set_current_language(@"ru"); 50 | XCTAssertTrue([testObject.testString isEqualToString:@"ru"]); 51 | 52 | dp_set_current_language(@"jp"); 53 | XCTAssertTrue([testObject.testString isEqualToString:preferredLanguage]); 54 | 55 | dp_set_current_language(nil); 56 | XCTAssertTrue([testObject.testString isEqualToString:preferredLanguage]); 57 | 58 | dp_set_current_language(@"de"); 59 | XCTAssertTrue([testObject.testString isEqualToString:@"de"]); 60 | 61 | [testObject removeAutolocalization]; 62 | XCTAssertFalse([testObject isAutolocalizationEnabled]); 63 | 64 | dp_set_current_language(@"ru"); 65 | XCTAssertTrue([testObject.testString isEqualToString:@"de"]); 66 | 67 | testObject = nil; 68 | } 69 | 70 | dp_set_current_language(nil); 71 | } 72 | 73 | - (void)testLocalizationArguments { 74 | XCTAssertTrue([[[DPLocalizationManager currentManager] defaultStringTableName] isEqualToString:@"Localizable"]); 75 | NSString *preferredLanguage = [DPLocalizationManager preferredLanguage]; 76 | XCTAssertNotNil(preferredLanguage); 77 | 78 | dp_set_current_language(@"en"); 79 | NSArray *args = @[@1, @2, @3]; 80 | NSArray *args_long = @[@1, @2, @3, @4, @5, @6]; 81 | NSArray *args_short = @[@1]; 82 | 83 | @autoreleasepool { 84 | TestObject *testObject = [[TestObject alloc] init]; 85 | [testObject setupAutolocalizationWithKey:@"TESTS_STRING_ARGS_1" keyPath:@"testString" arguments:args]; 86 | XCTAssertTrue([testObject isAutolocalizationEnabled]); 87 | XCTAssertTrue([testObject.testString isEqualToString:@"en 1 2 3"]); 88 | 89 | [testObject setupAutolocalizationWithKey:@"TESTS_STRING_ARGS_1" keyPath:@"testString" arguments:args_long]; 90 | XCTAssertTrue([testObject isAutolocalizationEnabled]); 91 | XCTAssertTrue([testObject.testString isEqualToString:@"en 1 2 3"]); 92 | 93 | XCTAssertThrows([testObject setupAutolocalizationWithKey:@"TESTS_STRING_ARGS_1" keyPath:@"testString" arguments:args_short]); 94 | 95 | testObject = nil; 96 | } 97 | 98 | @autoreleasepool { 99 | TestObject *testObject = [[TestObject alloc] init]; 100 | [testObject setupAutolocalizationWithKey:@"TESTS_STRING_ARGS_2" keyPath:@"testString" arguments:args]; 101 | XCTAssertTrue([testObject isAutolocalizationEnabled]); 102 | XCTAssertTrue([testObject.testString isEqualToString:@"en 3 1 2"]); 103 | 104 | [testObject setupAutolocalizationWithKey:@"TESTS_STRING_ARGS_2" keyPath:@"testString" arguments:args_long]; 105 | XCTAssertTrue([testObject isAutolocalizationEnabled]); 106 | XCTAssertTrue([testObject.testString isEqualToString:@"en 3 1 2"]); 107 | 108 | XCTAssertThrows([testObject setupAutolocalizationWithKey:@"TESTS_STRING_ARGS_2" keyPath:@"testString" arguments:args_short]); 109 | 110 | testObject = nil; 111 | } 112 | 113 | @autoreleasepool { 114 | TestObject *testObject = [[TestObject alloc] init]; 115 | [testObject setupAutolocalizationWithKey:@"TESTS_STRING_ARGS_3" keyPath:@"testString" arguments:args]; 116 | XCTAssertTrue([testObject isAutolocalizationEnabled]); 117 | XCTAssertTrue([testObject.testString isEqualToString:@"en 1 1 1"]); 118 | 119 | [testObject setupAutolocalizationWithKey:@"TESTS_STRING_ARGS_3" keyPath:@"testString" arguments:args_long]; 120 | XCTAssertTrue([testObject isAutolocalizationEnabled]); 121 | XCTAssertTrue([testObject.testString isEqualToString:@"en 1 1 1"]); 122 | 123 | [testObject setupAutolocalizationWithKey:@"TESTS_STRING_ARGS_3" keyPath:@"testString" arguments:args_short]; 124 | XCTAssertTrue([testObject isAutolocalizationEnabled]); 125 | XCTAssertTrue([testObject.testString isEqualToString:@"en 1 1 1"]); 126 | 127 | testObject = nil; 128 | } 129 | 130 | @autoreleasepool { 131 | TestObject *testObject = [[TestObject alloc] init]; 132 | XCTAssertThrows([testObject setupAutolocalizationWithKey:@"TESTS_STRING_ARGS_4" keyPath:@"testString" arguments:args]); 133 | 134 | [testObject setupAutolocalizationWithKey:@"TESTS_STRING_ARGS_4" keyPath:@"testString" arguments:args_long]; 135 | XCTAssertTrue([testObject isAutolocalizationEnabled]); 136 | XCTAssertTrue([testObject.testString isEqualToString:@"en 2 3 4"]); 137 | 138 | XCTAssertThrows([testObject setupAutolocalizationWithKey:@"TESTS_STRING_ARGS_4" keyPath:@"testString" arguments:args_short]); 139 | 140 | testObject = nil; 141 | } 142 | 143 | @autoreleasepool { 144 | TestObject *testObject = [[TestObject alloc] init]; 145 | [testObject setupAutolocalizationWithKey:@"TESTS_STRING_ARGS_2" keyPath:@"testString" arguments:args]; 146 | XCTAssertTrue([testObject isAutolocalizationEnabled]); 147 | XCTAssertTrue([testObject.testString isEqualToString:@"en 3 1 2"]); 148 | 149 | dp_set_current_language(@"ru"); 150 | XCTAssertTrue([testObject.testString isEqualToString:@"ru 3 1 2"]); 151 | 152 | dp_set_current_language(@"de"); 153 | XCTAssertTrue([testObject.testString isEqualToString:@"de 3 1 2"]); 154 | } 155 | } 156 | 157 | 158 | - (void)testBasicLocalizationPerformance { 159 | TestObject *testObject = [[TestObject alloc] init]; 160 | [self measureBlock:^{ 161 | for (int i = 0; i < 100; i++) { 162 | [testObject localizeWithLocalizationKey:@"TESTS_STRING" arguments:nil keyPath:@"testString"]; 163 | } 164 | }]; 165 | } 166 | 167 | - (void)testLocalizationArgumentsPerformance { 168 | TestObject *testObject = [[TestObject alloc] init]; 169 | [self measureBlock:^{ 170 | for (int i = 0; i < 100; i++) { 171 | [testObject localizeWithLocalizationKey:@"TESTS_STRING_ARGS_2" arguments:@[@1, @2, @3] keyPath:@"testString"]; 172 | } 173 | }]; 174 | } 175 | 176 | @end 177 | -------------------------------------------------------------------------------- /Example/Tests/BasicProxyTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // BasicProxyTests.m 3 | // LocalizationDemo 4 | // 5 | // Created by Dmitriy Petrusevich on 02/03/15. 6 | // Copyright (c) 2015 Dmitriy Petrusevich. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "DPLocalization.h" 12 | #import "TestObject.h" 13 | 14 | 15 | @interface BasicProxyTests : XCTestCase 16 | @end 17 | 18 | @implementation BasicProxyTests 19 | 20 | - (void)setUp { 21 | [super setUp]; 22 | [[DPLocalizationManager currentManager] setDefaultStringTableName:nil]; 23 | [[DPLocalizationManager currentManager] setCurrentLanguage:nil]; 24 | } 25 | 26 | - (void)testBasicLocalization { 27 | XCTAssertTrue([[[DPLocalizationManager currentManager] defaultStringTableName] isEqualToString:@"Localizable"]); 28 | NSString *preferredLanguage = [DPLocalizationManager preferredLanguage]; 29 | XCTAssertNotNil(preferredLanguage); 30 | 31 | // "TESTS_STRING" - language code 32 | 33 | @autoreleasepool { 34 | dp_set_current_language(@"en"); 35 | NSString *testString_1 = [DPAutolocalizationProxy autolocalizingStringWithLocalizationKey:@"TESTS_STRING"]; 36 | NSString *testString_2 = [DPAutolocalizationProxy autolocalizingStringWithLocalizationKey:@"TESTS_STRING" tableName:@"Localizable1"]; 37 | NSString *testPath = [DPAutolocalizationProxy autolocalizingPathForResource:@"image" ofType:@"png" inBundle:nil]; 38 | 39 | XCTAssertTrue([testString_1 isEqualToString:@"en"]); 40 | XCTAssertTrue([testString_2 isEqualToString:@"en 1"]); 41 | XCTAssertNotEqual([testPath rangeOfString:@"en.lproj"].location, NSNotFound); 42 | 43 | testString_1 = nil; 44 | testString_2 = nil; 45 | testPath = nil; 46 | } 47 | 48 | @autoreleasepool { 49 | NSString *pref_lproj = [preferredLanguage stringByAppendingString:@".lproj"]; 50 | dp_set_current_language(nil); 51 | 52 | TestObject *testObject = [[TestObject alloc] init]; 53 | testObject.testString = [DPAutolocalizationProxy autolocalizingStringWithLocalizationKey:@"TESTS_STRING"]; 54 | NSString *testPath = [DPAutolocalizationProxy autolocalizingPathForResource:@"image" ofType:@"png" inBundle:nil]; 55 | XCTAssertTrue([testObject.testString isEqualToString:preferredLanguage]); 56 | XCTAssertNotEqual([testPath rangeOfString:pref_lproj].location, NSNotFound); 57 | 58 | dp_set_current_language(@"en"); 59 | XCTAssertTrue([testObject.testString isEqualToString:@"en"]); 60 | XCTAssertNotEqual([testPath rangeOfString:@"en.lproj"].location, NSNotFound); 61 | 62 | dp_set_current_language(@"jp"); 63 | XCTAssertTrue([testObject.testString isEqualToString:preferredLanguage]); 64 | XCTAssertNotEqual([testPath rangeOfString:pref_lproj].location, NSNotFound); 65 | 66 | dp_set_current_language(@"ru"); 67 | XCTAssertTrue([testObject.testString isEqualToString:@"ru"]); 68 | XCTAssertNotEqual([testPath rangeOfString:@"ru.lproj"].location, NSNotFound); 69 | 70 | dp_set_current_language(nil); 71 | XCTAssertTrue([testObject.testString isEqualToString:preferredLanguage]); 72 | XCTAssertNotEqual([testPath rangeOfString:pref_lproj].location, NSNotFound); 73 | 74 | testObject = nil; 75 | } 76 | } 77 | 78 | - (void)testLocalizationArguments { 79 | XCTAssertTrue([[[DPLocalizationManager currentManager] defaultStringTableName] isEqualToString:@"Localizable"]); 80 | NSString *preferredLanguage = [DPLocalizationManager preferredLanguage]; 81 | XCTAssertNotNil(preferredLanguage); 82 | 83 | NSString *proxyString = [DPAutolocalizationProxy autolocalizingStringWithLocalizationKey:@"TESTS_STRING"]; 84 | NSArray *args = @[@1, proxyString, @3]; 85 | 86 | @autoreleasepool { 87 | NSString *pref_value = [NSString stringWithFormat:@"%@ 3 1 %@", preferredLanguage, preferredLanguage]; 88 | 89 | TestObject *testObject = [[TestObject alloc] init]; 90 | [testObject setupAutolocalizationWithKey:@"TESTS_STRING_ARGS_2" keyPath:@"testString" arguments:args]; 91 | XCTAssertTrue([testObject isAutolocalizationEnabled]); 92 | 93 | dp_set_current_language(@"en"); 94 | XCTAssertTrue([testObject.testString isEqualToString:@"en 3 1 en"]); 95 | 96 | dp_set_current_language(@"ru"); 97 | XCTAssertTrue([testObject.testString isEqualToString:@"ru 3 1 ru"]); 98 | 99 | dp_set_current_language(@"jp"); 100 | XCTAssertTrue([testObject.testString isEqualToString:pref_value]); 101 | 102 | dp_set_current_language(@"de"); 103 | XCTAssertTrue([testObject.testString isEqualToString:@"de 3 1 de"]); 104 | 105 | [testObject removeAutolocalization]; 106 | 107 | dp_set_current_language(@"ru"); 108 | XCTAssertTrue([testObject.testString isEqualToString:@"de 3 1 de"]); 109 | 110 | testObject = nil; 111 | } 112 | 113 | dp_set_current_language(nil); 114 | } 115 | 116 | #pragma mark - 117 | 118 | - (void)testPostNotification { 119 | XCTestExpectation *expectation = [self expectationWithDescription:@"Testing ... set new language \"Post Notification\" order"]; 120 | 121 | [[DPLocalizationManager currentManager] setCurrentLanguage:nil]; 122 | XCTAssertNil([[DPLocalizationManager currentManager] currentLanguage]); 123 | 124 | NSMutableString *orderString = [NSMutableString string]; 125 | 126 | id observer1 = [[DPAutolocalizationProxy notificationCenter] addObserverForName:DPLanguageDidChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { 127 | [orderString appendString:@"1"]; 128 | }]; 129 | 130 | id observer2 = [[NSNotificationCenter defaultCenter] addObserverForName:DPLanguageDidChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { 131 | [orderString appendString:@"2"]; 132 | [expectation fulfill]; 133 | }]; 134 | 135 | 136 | NSString *newLng = @"ru"; 137 | [[DPLocalizationManager currentManager] setCurrentLanguage:newLng]; 138 | 139 | [self waitForExpectationsWithTimeout:1.0 handler:^(NSError *error) { 140 | [[DPAutolocalizationProxy notificationCenter] removeObserver:observer1]; 141 | [[NSNotificationCenter defaultCenter] removeObserver:observer2]; 142 | 143 | if (error) { 144 | XCTFail(@"Expectation Failed with error: %@", error); 145 | } 146 | else { 147 | XCTAssertTrue([[[DPLocalizationManager currentManager] currentLanguage] isEqualToString:newLng]); 148 | XCTAssertTrue([orderString isEqualToString:@"12"]); 149 | } 150 | }]; 151 | } 152 | 153 | @end 154 | -------------------------------------------------------------------------------- /Example/Tests/TestObject.h: -------------------------------------------------------------------------------- 1 | // 2 | // TestObject.h 3 | // LocalizationDemo 4 | // 5 | // Created by Dmitriy Petrusevich on 02/03/15. 6 | // Copyright (c) 2015 Dmitriy Petrusevich. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TestObject : NSObject 12 | @property (nonatomic, copy) NSString *testString; 13 | @end 14 | -------------------------------------------------------------------------------- /Example/Tests/TestObject.m: -------------------------------------------------------------------------------- 1 | // 2 | // TestObject.m 3 | // LocalizationDemo 4 | // 5 | // Created by Dmitriy Petrusevich on 02/03/15. 6 | // Copyright (c) 2015 Dmitriy Petrusevich. All rights reserved. 7 | // 8 | 9 | #import "TestObject.h" 10 | 11 | @implementation TestObject 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Example/Tests/iOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Example/Tests/iOS/UIKitExtensionTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIKitExtensionTests.m 3 | // LocalizationDemo 4 | // 5 | // Created by Dmitriy Petrusevich on 02/03/15. 6 | // Copyright (c) 2015 Dmitriy Petrusevich. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "DPLocalization.h" 12 | 13 | 14 | @interface UIKitExtensionTests : XCTestCase 15 | @end 16 | 17 | @implementation UIKitExtensionTests 18 | 19 | - (void)setUp { 20 | [super setUp]; 21 | [[DPLocalizationManager currentManager] setDefaultStringTableName:nil]; 22 | [[DPLocalizationManager currentManager] setCurrentLanguage:nil]; 23 | } 24 | 25 | - (void)testAutolocalizationKey { 26 | XCTAssertTrue([[[DPLocalizationManager currentManager] defaultStringTableName] isEqualToString:@"Localizable"]); 27 | NSString *preferredLanguage = [DPLocalizationManager preferredLanguage]; 28 | XCTAssertNotNil(preferredLanguage); 29 | 30 | // "TESTS_STRING" - language code 31 | dp_set_current_language(nil); 32 | 33 | @autoreleasepool { 34 | UILabel *label = [[UILabel alloc] init]; label.autolocalizationKey = @"TESTS_STRING"; 35 | UIButton *buton = [UIButton buttonWithType:UIButtonTypeCustom]; buton.autolocalizationKey = @"TESTS_STRING"; 36 | UIBarButtonItem *barButton = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:NULL]; barButton.autolocalizationKey = @"TESTS_STRING"; 37 | UITextField *textField = [[UITextField alloc] init]; textField.autolocalizationKey = @"TESTS_STRING"; 38 | UITextView *textView = [[UITextView alloc] init]; textView.autolocalizationKey = @"TESTS_STRING"; 39 | UISearchBar *searchBar = [[UISearchBar alloc] init]; searchBar.autolocalizationKey = @"TESTS_STRING"; 40 | UIViewController *viewController = [[UIViewController alloc] init]; viewController.autolocalizationKey = @"TESTS_STRING"; 41 | 42 | XCTAssertTrue([label.text isEqualToString:preferredLanguage]); 43 | XCTAssertTrue([[buton titleForState:UIControlStateNormal] isEqualToString:preferredLanguage]); 44 | XCTAssertTrue([barButton.title isEqualToString:preferredLanguage]); 45 | XCTAssertTrue([textField.placeholder isEqualToString:preferredLanguage]); 46 | XCTAssertTrue([textView.text isEqualToString:preferredLanguage]); 47 | XCTAssertTrue([searchBar.placeholder isEqualToString:preferredLanguage]); 48 | XCTAssertTrue([viewController.title isEqualToString:preferredLanguage]); 49 | 50 | dp_set_current_language(@"ru"); 51 | XCTAssertTrue([label.text isEqualToString:@"ru"]); 52 | XCTAssertTrue([[buton titleForState:UIControlStateNormal] isEqualToString:@"ru"]); 53 | XCTAssertTrue([barButton.title isEqualToString:@"ru"]); 54 | XCTAssertTrue([textField.placeholder isEqualToString:@"ru"]); 55 | XCTAssertTrue([textView.text isEqualToString:@"ru"]); 56 | XCTAssertTrue([searchBar.placeholder isEqualToString:@"ru"]); 57 | XCTAssertTrue([viewController.title isEqualToString:@"ru"]); 58 | 59 | dp_set_current_language(nil); 60 | XCTAssertTrue([label.text isEqualToString:preferredLanguage]); 61 | XCTAssertTrue([[buton titleForState:UIControlStateNormal] isEqualToString:preferredLanguage]); 62 | XCTAssertTrue([barButton.title isEqualToString:preferredLanguage]); 63 | XCTAssertTrue([textField.placeholder isEqualToString:preferredLanguage]); 64 | XCTAssertTrue([textView.text isEqualToString:preferredLanguage]); 65 | XCTAssertTrue([searchBar.placeholder isEqualToString:preferredLanguage]); 66 | XCTAssertTrue([viewController.title isEqualToString:preferredLanguage]); 67 | 68 | dp_set_current_language(@"de"); 69 | XCTAssertTrue([label.text isEqualToString:@"de"]); 70 | XCTAssertTrue([[buton titleForState:UIControlStateNormal] isEqualToString:@"de"]); 71 | XCTAssertTrue([barButton.title isEqualToString:@"de"]); 72 | XCTAssertTrue([textField.placeholder isEqualToString:@"de"]); 73 | XCTAssertTrue([textView.text isEqualToString:@"de"]); 74 | XCTAssertTrue([searchBar.placeholder isEqualToString:@"de"]); 75 | XCTAssertTrue([viewController.title isEqualToString:@"de"]); 76 | } 77 | 78 | dp_set_current_language(nil); 79 | } 80 | 81 | @end 82 | -------------------------------------------------------------------------------- /Generator/parser.py: -------------------------------------------------------------------------------- 1 | __author__ = 'dmitriypetrusevich' 2 | 3 | import xml.etree.ElementTree as ElementTree 4 | import re 5 | 6 | re_find_mod = re.compile("([a-z])\s%\s([0-9]+)") 7 | re_find_equal = re.compile("([a-z0-9_]+?)\s=\s([0-9\.,]+)") 8 | re_find_not_equal = re.compile("([a-z0-9_]+?)\s!=\s([0-9\.,]+)") 9 | re_find_range = re.compile("([0-9]+)\.\.([0-9]+)") 10 | 11 | source_header = """/* 12 | * 13 | * This is generated source file from 'plural.xml'. 14 | * http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html 15 | * http://unicode.org/cldr/trac/browser/trunk/common/supplemental/plurals.xml 16 | * 17 | * 18 | * n - absolute value of the source number (integer and decimals). 19 | * i - integer digits of n. 20 | * v - number of visible fraction digits in n, with trailing zeros. 21 | * w - number of visible fraction digits in n, without trailing zeros. 22 | * f - visible fractional digits in n, with trailing zeros. 23 | * t - visible fractional digits in n, without trailing zeros. 24 | * 25 | */ 26 | 27 | #include 28 | #include "dp_gen_plural.h" 29 | 30 | 31 | enum DPPluralRule dp_plural_rules_always_other(double n, int i, int v, int w, int f, int t) { 32 | return DPPluralRuleOther; 33 | } 34 | 35 | """ 36 | 37 | header_header = """/* 38 | * 39 | * This is generated source file from 'plural.xml'. 40 | * http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html 41 | * http://unicode.org/cldr/trac/browser/trunk/common/supplemental/plurals.xml 42 | * 43 | * 44 | * n - absolute value of the source number (integer and decimals). 45 | * i - integer digits of n. 46 | * v - number of visible fraction digits in n, with trailing zeros. 47 | * w - number of visible fraction digits in n, without trailing zeros. 48 | * f - visible fractional digits in n, with trailing zeros. 49 | * t - visible fractional digits in n, without trailing zeros. 50 | * 51 | */ 52 | 53 | enum DPPluralRule { 54 | DPPluralRuleUnknown, 55 | 56 | DPPluralRuleZero, 57 | DPPluralRuleOne, 58 | DPPluralRuleTwo, 59 | DPPluralRuleFew, 60 | DPPluralRuleMany, 61 | DPPluralRuleOther 62 | }; 63 | 64 | typedef enum DPPluralRule(*dp_plural_rules_func)(double n, int i, int v, int w, int f, int t); 65 | 66 | enum DPPluralRule dp_plural_rules_always_other(double n, int i, int v, int w, int f, int t); 67 | dp_plural_rules_func dp_plural_rules_for_lang(const char *lang_code); 68 | 69 | 70 | """ 71 | 72 | 73 | # parse plural.xml 74 | def parse_xml(): 75 | root_el = ElementTree.parse('plurals.xml').getroot() 76 | 77 | for form in root_el.findall('plurals'): 78 | if form.attrib['type'] == 'cardinal': 79 | plural = form 80 | break 81 | 82 | collections = plural.findall('pluralRules') 83 | 84 | rules_by_locale = {} 85 | 86 | for rule_collection in collections: 87 | locales = rule_collection.attrib['locales'].split() 88 | rules = rule_collection.findall('pluralRule') 89 | rules_dict = {} 90 | 91 | for rule in rules: 92 | count = rule.attrib['count'] 93 | 94 | sym_index = rule.text.find('@') 95 | if sym_index != -1: 96 | rules_dict[count] = rule.text[0:sym_index].strip() 97 | else: 98 | rules_dict[count] = rule.text.strip() 99 | 100 | for locale in locales: 101 | rules_by_locale[locale] = rules_dict 102 | 103 | return rules_by_locale 104 | 105 | 106 | # generate source file 107 | def generate_source(rules, lang_code): 108 | pre_calc_vars = {} 109 | 110 | condition_zero = generate_condition_string(rules.get('zero'), pre_calc_vars) 111 | condition_one = generate_condition_string(rules.get('one'), pre_calc_vars) 112 | condition_two = generate_condition_string(rules.get('two'), pre_calc_vars) 113 | condition_few = generate_condition_string(rules.get('few'), pre_calc_vars) 114 | condition_many = generate_condition_string(rules.get('many'), pre_calc_vars) 115 | 116 | decl_string = "enum DPPluralRule dp_plural_rules_" + lang_code + "(double n, int i, int v, int w, int f, int t)" 117 | impl_string = "" 118 | 119 | if condition_zero is not None: 120 | impl_string += "\tif (" + condition_zero + ") { // " + rules.get( 121 | 'zero') + "\n\t\treturn DPPluralRuleZero;\n\t}\n" 122 | if condition_one is not None: 123 | impl_string += "\tif (" + condition_one + ") { // " + rules.get('one') + "\n\t\treturn DPPluralRuleOne;\n\t}\n" 124 | if condition_two is not None: 125 | impl_string += "\tif (" + condition_two + ") { // " + rules.get('two') + "\n\t\treturn DPPluralRuleTwo;\n\t}\n" 126 | if condition_few is not None: 127 | impl_string += "\tif (" + condition_few + ") { // " + rules.get('few') + "\n\t\treturn DPPluralRuleFew;\n\t}\n" 128 | if condition_many is not None: 129 | impl_string += "\tif (" + condition_many + ") { // " + rules.get( 130 | 'many') + "\n\t\treturn DPPluralRuleMany;\n\t}\n" 131 | 132 | impl_string += "\treturn DPPluralRuleOther;\n}\n" 133 | 134 | var_str = "" 135 | for key in pre_calc_vars: 136 | var_str += ("\t" + pre_calc_vars[key] + "\n") 137 | 138 | impl_string = (decl_string + " {\n" + var_str + "\n" + impl_string) 139 | decl_string += ";" 140 | 141 | return decl_string, impl_string 142 | 143 | 144 | # generate condition string from rule string and set pre-calculated variables 145 | def generate_condition_string(rule_string, pre_calc_vars): 146 | if rule_string is None: 147 | return None 148 | 149 | and_idx = rule_string.find("and") 150 | or_idx = rule_string.find("or") 151 | 152 | if and_idx != -1 and or_idx != -1: 153 | result_string = "(" + rule_string.replace("and", "&&").replace(" or ", ") || (") + ")" 154 | else: 155 | result_string = rule_string.replace("and", "&&").replace("or", "||") 156 | 157 | # module (a % b) 158 | matches = reversed(list(re_find_mod.finditer(result_string))) 159 | for match in matches: 160 | match_range = match.regs[0] 161 | var_name = result_string[match.regs[1][0]:match.regs[1][1]] 162 | mod_value = result_string[match.regs[2][0]:match.regs[2][1]] 163 | 164 | gen_var_name = var_name + "_mod_" + mod_value 165 | pre_calc_vars[gen_var_name] = "int " + gen_var_name + " = (int)" + var_name + ' % ' + mod_value + ';' 166 | result_string = result_string[:match_range[0]] + gen_var_name + result_string[match_range[1]:] 167 | 168 | # in range (a == b..c,d..e,f) 169 | matches = reversed(list(re_find_equal.finditer(result_string))) 170 | for match in matches: 171 | match_range = match.span() 172 | var_name = match.group(1) 173 | vals_list = match.group(2).split(',') 174 | 175 | substr = "" 176 | for val in vals_list: 177 | if len(substr) != 0: 178 | substr += ' || ' 179 | 180 | range_match = re_find_range.search(val) 181 | if range_match is None: 182 | substr += (var_name + " == " + val) 183 | else: 184 | val_from = range_match.group(1) 185 | val_to = range_match.group(2) 186 | substr += "(" + var_name + ' >= ' + val_from + ' && ' + var_name + ' <= ' + val_to + ")" 187 | 188 | if len(vals_list) > 1: 189 | substr = "(" + substr + ")" 190 | 191 | result_string = result_string[:match_range[0]] + substr + result_string[match_range[1]:] 192 | 193 | # not in range (a != b..c,d..e,f) 194 | matches = reversed(list(re_find_not_equal.finditer(result_string))) 195 | for match in matches: 196 | match_range = match.span() 197 | var_name = match.group(1) 198 | vals_list = match.group(2).split(',') 199 | 200 | substr = "" 201 | for val in vals_list: 202 | if len(substr) != 0: 203 | substr += ' && ' 204 | 205 | range_match = re_find_range.search(val) 206 | if range_match is None: 207 | substr += (var_name + " != " + val) 208 | else: 209 | val_from = range_match.group(1) 210 | val_to = range_match.group(2) 211 | substr += "!(" + var_name + ' >= ' + val_from + ' && ' + var_name + ' <= ' + val_to + ")" 212 | 213 | if len(vals_list) > 1: 214 | substr = "(" + substr + ")" 215 | 216 | result_string = result_string[:match_range[0]] + substr + result_string[match_range[1]:] 217 | 218 | # return result string (I know, this is useless comment) 219 | return result_string 220 | 221 | 222 | result = parse_xml() 223 | codes = sorted(result.keys()) 224 | 225 | result_source_file = source_header 226 | result_header_file = header_header 227 | 228 | result_source_file += "dp_plural_rules_func dp_plural_rules_for_lang(const char *lang_code) {\n" 229 | for code in codes: 230 | cond_str = '\tif (strncmp(lang_code, "' + code + '", ' + str(len(code)) + ') == 0) return dp_plural_rules_' + code + ';\n' 231 | result_source_file += cond_str 232 | 233 | result_source_file += "\treturn NULL;\n}\n\n" 234 | 235 | for code in codes: 236 | (decl, impl) = generate_source(result[code], code) 237 | result_source_file += (impl + '\n') 238 | result_header_file += (decl + '\n') 239 | 240 | src_file = open("dp_gen_plural.h", "w") 241 | src_file.write(result_header_file) 242 | src_file.close() 243 | 244 | src_file = open("dp_gen_plural.c", "w") 245 | src_file.write(result_source_file) 246 | src_file.close() 247 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 nullic 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "DPLocalization", 7 | platforms: [ 8 | .iOS(.v9) 9 | ], 10 | products: [ 11 | .library( 12 | name: "DPLocalization", targets: ["DPLocalization"] 13 | ) 14 | ], 15 | dependencies: [], 16 | targets: [ 17 | .target(name: "DPLocalization", dependencies: [], path: "DPLocalization", exclude: []) 18 | ] 19 | ) 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/nullic/DPLocalizationManager.svg)](https://travis-ci.org/nullic/DPLocalizationManager) 2 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 3 | [![Swift Package Manager compatible](https://img.shields.io/badge/Swift%20Package%20Manager-compatible-brightgreen.svg)](https://github.com/apple/swift-package-manager) 4 | [![Version](https://img.shields.io/cocoapods/v/DPLocalization.svg?style=flat)](http://cocoapods.org/pods/DPLocalization) 5 | [![License](https://img.shields.io/cocoapods/l/DPLocalization.svg?style=flat)](http://cocoapods.org/pods/DPLocalization) 6 | [![Platform](https://img.shields.io/cocoapods/p/DPLocalization.svg?style=flat)](http://cocoapods.org/pods/DPLocalization) 7 | 8 | 9 | # DPLocalization 10 | 11 | DPLocalization provide easy enough way to change localization inside application. 12 | 13 | 14 | ## Usage 15 | 16 | ###Dynamic localization 17 | 18 | 1. Use ```autolocalizationKey``` property in code or as **user defined runtime attribute** in xib: 19 | 20 | ``` 21 | myLabel.autolocalizationKey = @"my_label_localization_key"; 22 | myButton.autolocalizationKey = @"my_button_name"; 23 | ``` 24 | 25 | 2. Use ```-[setupAutolocalizationWithKey:keyPath:]``` or ```-[setupAutolocalizationWithKey:keyPath:arguments:]``` method of **NSObject**: 26 | 27 | ``` 28 | [myLabel setupAutolocalizationWithKey:@"my_label_localization_key" keyPath:@"text"]; 29 | [myLabel setupAutolocalizationWithKey:@"my_label_localization_template_key" keyPath:@"text" arguments:@[@"first", @"second"]]; 30 | ``` 31 | 32 | Now after changing language (for example: ```dp_set_current_language(@"de")```) all objects, which was configured for dynamic localization, update their content. 33 | 34 | 35 | ###Static localization 36 | 37 | For static localization select language (for example: ```dp_set_current_language(@"de")```) and use ```DPLocalizedString()``` macro in ```NSLocalizedString()``` maner. 38 | 39 | 40 | ###Proxy objects 41 | 42 | Use **DPAutolocalizationProxy** class methods or their replacements. 43 | 44 | ``` 45 | NSString *str = [NSString autolocalizingStringWithLocalizationKey:@"language"]; 46 | 47 | dp_set_current_language(@"en"); 48 | CFShow((__bridge CFTypeRef)(str)); 49 | dp_set_current_language(@"ru"); 50 | CFShow((__bridge CFTypeRef)(str)); 51 | ``` 52 | 53 | Output example: 54 | ``` 55 | English 56 | Russian 57 | ``` 58 | 59 | 60 | ## Plural rules 61 | 62 | [List of all supported languages.](http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html) Currently library suported only cardinal type. 63 | Library use .stringsdict files ([Stringsdict File Format](https://developer.apple.com/library/ios/documentation/MacOSX/Conceptual/BPInternational/StringsdictFileFormat/StringsdictFileFormat.html)), with little changes: 64 | * ***NSStringFormatValueTypeKey*** is ignored 65 | * ***format specifiers*** must be 'Objective-C object' 66 | 67 | 68 | 69 | ## Attributed strings 70 | 71 | Some contols (since iOS 6) are support attributed values. You can specify attributes using special format (<**attributes**>{**string**}), it also reqire to set ```isAttributedKey``` property to ```YES```. Attributes will apply for string inside {}, other parts will use parameters specified by control. For example: 72 | ``` 73 | /* Localizable.strings */ 74 | "TITLE" = "English {(en)}"; 75 | ``` 76 | ``` 77 | UILabel *myLabel = [[UILabel alloc] initWithFrame:CGRectZero]; 78 | myLabel.autolocalizationKey = @"TITLE"; 79 | myLabel.isAttributedKey = YES; 80 | ``` 81 | Will set ```attributedText``` property with string that can be described as: 82 | ``` 83 | English { 84 | NSColor = "UIDeviceWhiteColorSpace 0 1"; 85 | NSFont = " font-family: \".HelveticaNeueInterface-Regular\"; font-weight: normal; font-style: normal; font-size: 17.00pt"; 86 | }(en){ 87 | NSColor = "UIDeviceRGBColorSpace 0.0470588 0.219608 0.741176 1"; 88 | NSFont = " font-family: \".HelveticaNeueInterface-Regular\"; font-weight: normal; font-style: normal; font-size: 12.00pt"; 89 | } 90 | ``` 91 | 92 | ### Avalible attributes: 93 | * **name** - set font name - ```{string}``` 94 | * **size** - set font size - ```{string}``` or ```{string}``` 95 | * **color** - set text color in RGB or RGBA format - ```{string}``` or ```{string}``` 96 | * **traits** - set font traits: **b** (bold), **i** (italic); and text attributes **u** (underline), **s** (strikethrough), **m** (monospace); **!** used for remove attibute - ```{string}``` or ```{string}``` 97 | * **kern** - set font kerning 98 | * **link** - set link attribute - ```{link to DPLocalizationManager}``` 99 | * **spacing** - set space between paragraphs 100 | * **linespacing** - set space between lines 101 | * **alignment** - set alignment of paragraph {Center\n}{Left}. Valid values: **left** **center** **right** **justified** **natural**. Due to NSParagraphStyle behaviour each paragraph should be ended with **\n** and each section should have alignment if it is used in any place of localization string. 102 | 103 | 104 | 105 | ## Replacements 106 | You can specify replacement using next format: [<**replacer**>]. This feature also reqire set ```isAttributedKey``` property to ```YES```. 107 | Example: 108 | ``` 109 | /* Localizable.strings */ 110 | "TITLE_WITH_IMAGE" = "Title []"; 111 | ``` 112 | ``` 113 | UILabel *myLabel = [[UILabel alloc] initWithFrame:CGRectZero]; 114 | myLabel.autolocalizationKey = @"TITLE_WITH_IMAGE"; 115 | myLabel.isAttributedKey = YES; 116 | ``` 117 | 118 | ### Avalible replacers: 119 | * **img** - insert image named - ```[ size=(16,16) offset=(0,0)]``` - **size** and **offset** are optinal parameters. By default **size** is (image.width, image.height); **offset** is (0, font.descender) 120 | 121 | 122 | ## Installation 123 | 124 | **CocoaPods** 125 | 126 | DPLocalization is available through [CocoaPods](http://cocoapods.org), to install 127 | it simply add the following line to your Podfile: 128 | 129 | pod "DPLocalization" 130 | 131 | **Carthage** 132 | 133 | To integrate DPLocalization into your Xcode project using Carthage, specify it in your `Cartfile`: 134 | 135 | github "nullic/DPLocalizationManager" 136 | 137 | Run `carthage update` to build the framework and drag the built `DPLocalization.framework` into your Xcode project. 138 | 139 | 140 | ## Localization check 141 | 142 | dpstrings.py it is Python script, that used for checking most common usage of library. It's scan {some} folder for sources and resoures and make validation for common errors (missing keys, duplication). 143 | 144 | Usage example: **python dpstrings.py -p "MyProjectPath" -l "en;ru;"**.
145 | For more information: **python dpstrings.py --help**. 146 | 147 | 148 | ## License 149 | 150 | DPLocalization is available under the MIT license. See the LICENSE file for more info. 151 | -------------------------------------------------------------------------------- /dpstrings.py: -------------------------------------------------------------------------------- 1 | import io 2 | import argparse 3 | import codecs 4 | import os 5 | import re 6 | 7 | class DPStringExtractor: 8 | pattern_src_1 = re.compile("(?<=DPLocalizedString\(@\").*?(?=\")") 9 | pattern_src_2 = re.compile(".*?\.autolocalizationKey\s*=\s*@?\"(.*?)\"") 10 | pattern_src_3 = re.compile("\[.*?\ssetAutolocalizationKey:\s*@\"(.*?)\"\]") 11 | pattern_src_4 = re.compile(".*?\sautolocalizationKey\s*=\s*@?\"(.*?)\"") 12 | pattern_src_5 = re.compile("(?<=DPLocalizedString\(\").*?(?=\")") 13 | 14 | pattern_xib_1 = re.compile( 15 | "autolocalizationKey\s*(.*?)") 16 | pattern_xib_2 = re.compile( 17 | "") 18 | 19 | @staticmethod 20 | def is_source_file(filename): 21 | extension = os.path.splitext(filename)[1].lower() 22 | return extension == ".m" or extension == ".mm" or extension == ".swift" 23 | 24 | @staticmethod 25 | def is_xib_file(filename): 26 | extension = os.path.splitext(filename)[1].lower() 27 | return extension == ".xib" or extension == ".storyboard" 28 | 29 | def __init__(self): 30 | self.strings_info = {} 31 | 32 | def add_strings(self, strings, path): 33 | for string in strings: 34 | if not self.strings_info.has_key(string): 35 | self.strings_info[string] = [] 36 | self.strings_info[string].append(path) 37 | 38 | def extract_strings(self, path, patterns): 39 | all_results = [] 40 | text = codecs.open(path, encoding='utf-8').read() 41 | for pattern in patterns: 42 | result = pattern.findall(text) 43 | if len(result): 44 | all_results.extend(result) 45 | self.add_strings(all_results, path) 46 | 47 | def scan_dir(self, dir_path): 48 | files = os.listdir(dir_path) 49 | 50 | for filename in files: 51 | full_path = os.path.join(dir_path, filename) 52 | if os.path.isfile(full_path): 53 | patterns = None 54 | if DPStringExtractor.is_source_file(filename): 55 | patterns = [self.pattern_src_1, self.pattern_src_2, self.pattern_src_3, self.pattern_src_4, self.pattern_src_5] 56 | elif DPStringExtractor.is_xib_file(filename): 57 | patterns = [self.pattern_xib_1, self.pattern_xib_2] 58 | 59 | if patterns is not None: 60 | self.extract_strings(full_path, patterns) 61 | else: 62 | self.scan_dir(full_path) 63 | 64 | return self.strings_info 65 | 66 | 67 | class DPLocalizableStringsParser: 68 | localizable_str_1 = re.compile("\"(.*?)\"\s*=\s*\"(.*?)\";") 69 | 70 | @staticmethod 71 | def is_strings_file(filename): 72 | return filename == "Localizable.strings" 73 | #extension = os.path.splitext(filename)[1].lower() 74 | #return extension == ".strings" 75 | 76 | def __init__(self): 77 | self.strings_info = {} 78 | 79 | def extract_strings(self, path, patterns, nil=None): 80 | locale_pattern = re.compile("(?<=/)[a-zA-Z_]*(?=\.lproj/)") 81 | locale = locale_pattern.findall(path)[0] 82 | 83 | try: 84 | text = io.open(path, 'r', encoding = 'utf-8').read() 85 | except UnicodeDecodeError: 86 | text = None 87 | 88 | if text == None: 89 | try: 90 | text = io.open(path, 'r', encoding = 'utf-16').read() 91 | except UnicodeError: 92 | text = None 93 | print u"WARNING: File \"{0}\" contains non UTF-8/UTF-16 characters, and will be ignored".format(path) 94 | 95 | if text != None: 96 | for pattern in patterns: 97 | result = pattern.findall(text) 98 | for kv_tuple in result: 99 | if len(kv_tuple) == 2: 100 | key, value = kv_tuple 101 | 102 | if not self.strings_info.has_key(key): 103 | self.strings_info[key] = {} 104 | 105 | if not self.strings_info[key].has_key(locale): 106 | self.strings_info[key][locale] = value 107 | else: 108 | print u"WARNING: Duplicate key: \"{0} in file \"{1}\"".format(key, path) 109 | 110 | 111 | 112 | 113 | def scan_dir(self, dir_path): 114 | files = os.listdir(dir_path) 115 | 116 | for filename in files: 117 | full_path = os.path.join(dir_path, filename) 118 | if os.path.isfile(full_path): 119 | patterns = None 120 | if DPLocalizableStringsParser.is_strings_file(filename): 121 | patterns = [self.localizable_str_1] 122 | if patterns is not None: 123 | self.extract_strings(full_path, patterns) 124 | else: 125 | self.scan_dir(full_path) 126 | 127 | return self.strings_info 128 | 129 | 130 | if __name__ == '__main__': 131 | parser = argparse.ArgumentParser(description='''Usage example: dpstrings.py -p "./Project" -l "en;ru;"''') 132 | parser.add_argument("-l", help='''List of localizations (i.e. ru, en, fr, de) separated by ';'. Default value is "en"''', default="en") 133 | parser.add_argument("-p", help='''Search path. Default value is "./"''', default="./") 134 | args = parser.parse_args() 135 | 136 | search_path = args.p 137 | locales = set() 138 | for loc in args.l.split(";"): 139 | loc = loc.strip() 140 | if len(loc): 141 | locales.add(loc) 142 | 143 | used_strings = DPStringExtractor().scan_dir(search_path) 144 | local_strings = DPLocalizableStringsParser().scan_dir(search_path) 145 | 146 | sorted_keys = sorted(local_strings) 147 | sorted_used_strings = sorted(used_strings) 148 | 149 | for key in sorted_keys: 150 | string_info = local_strings[key] 151 | string_locales = string_info.keys() 152 | if not locales.issubset(string_locales): 153 | print u"WARNING: Not enough localizations for key: {0}. Exist locales: {1}".format(key, string_info.keys()) 154 | 155 | for key in sorted_keys: 156 | if not used_strings.has_key(key): 157 | print u"WARNING: Not used key: \"{0}\"".format(key) 158 | 159 | for key in sorted_used_strings: 160 | if not local_strings.has_key(key): 161 | paths = u"; ".join(set(used_strings[key])) 162 | print u"WARNING: No localization for key: \"{0}\" in {1}".format(key, paths) 163 | --------------------------------------------------------------------------------