diff --git a/Lin/LNAlertAccessoryView.h b/Lin/LNAlertAccessoryView.h index 3df161d..355988a 100644 --- a/Lin/LNAlertAccessoryView.h +++ b/Lin/LNAlertAccessoryView.h @@ -18,5 +18,6 @@ @property (nonatomic, copy, readonly) LNLocalizationCollection *selectedCollection; @property (nonatomic, copy, readonly) NSString *inputtedKey; @property (nonatomic, copy, readonly) NSString *inputtedValue; +@property (nonatomic, copy, readonly) NSString *inputtedComment; @end diff --git a/Lin/LNAlertAccessoryView.m b/Lin/LNAlertAccessoryView.m index 78b1c14..fb3658c 100644 --- a/Lin/LNAlertAccessoryView.m +++ b/Lin/LNAlertAccessoryView.m @@ -17,6 +17,7 @@ @interface LNAlertAccessoryView () @property (weak) IBOutlet NSPopUpButton *languageButton; @property (weak) IBOutlet NSTextField *keyTextField; @property (weak) IBOutlet NSTextField *valueTextField; +@property (weak) IBOutlet NSTextField *commentTextField; @end @@ -71,6 +72,10 @@ - (NSString *)inputtedValue return self.valueTextField.stringValue; } +- (NSString *)inputtedComment +{ + return self.commentTextField.stringValue; +} #pragma mark - Actions @@ -123,6 +128,7 @@ - (void)configureButton { NSString *key = self.keyTextField.stringValue; NSString *value = self.valueTextField.stringValue; + // comment is optional [self.button setEnabled:(self.collections.count > 0 && key.length > 0 && value.length > 0)]; } diff --git a/Lin/LNAlertAccessoryView.xib b/Lin/LNAlertAccessoryView.xib index 4ee3fe0..f6075c4 100644 --- a/Lin/LNAlertAccessoryView.xib +++ b/Lin/LNAlertAccessoryView.xib @@ -1,8 +1,8 @@ - + - - + + @@ -13,11 +13,11 @@ - + - + @@ -26,7 +26,7 @@ - + @@ -35,7 +35,7 @@ - + @@ -44,7 +44,7 @@ - + @@ -52,8 +52,17 @@ + + + + + + + + + - + @@ -71,7 +80,7 @@ - + @@ -86,7 +95,7 @@ - + @@ -98,7 +107,7 @@ - + @@ -109,8 +118,21 @@ + + + + + + + + + + + + + @@ -118,4 +140,4 @@ - \ No newline at end of file + diff --git a/Lin/LNLocalization.h b/Lin/LNLocalization.h index 147dd85..c4de6fe 100644 --- a/Lin/LNLocalization.h +++ b/Lin/LNLocalization.h @@ -14,6 +14,7 @@ @property (nonatomic, copy, readonly) NSString *key; @property (nonatomic, copy, readonly) NSString *value; +@property (nonatomic, copy, readonly) NSString *comment; @property (nonatomic, assign, readonly) NSRange entityRange; @property (nonatomic, assign, readonly) NSRange keyRange; @@ -21,8 +22,8 @@ @property (nonatomic, weak, readonly) LNLocalizationCollection *collection; -+ (instancetype)localizationWithKey:(NSString *)key value:(NSString *)value entityRange:(NSRange)entityRange keyRange:(NSRange)keyRange valueRange:(NSRange)valueRange collection:(LNLocalizationCollection *)collection; ++ (instancetype)localizationWithKey:(NSString *)key value:(NSString *)value comment:(NSString *)comment entityRange:(NSRange)entityRange keyRange:(NSRange)keyRange valueRange:(NSRange)valueRange collection:(LNLocalizationCollection *)collection; -- (instancetype)initWithKey:(NSString *)key value:(NSString *)value entityRange:(NSRange)entityRange keyRange:(NSRange)keyRange valueRange:(NSRange)valueRange collection:(LNLocalizationCollection *)collection; +- (instancetype)initWithKey:(NSString *)key value:(NSString *)value comment:(NSString *)comment entityRange:(NSRange)entityRange keyRange:(NSRange)keyRange valueRange:(NSRange)valueRange collection:(LNLocalizationCollection *)collection; @end diff --git a/Lin/LNLocalization.m b/Lin/LNLocalization.m index 411f0cf..683432f 100644 --- a/Lin/LNLocalization.m +++ b/Lin/LNLocalization.m @@ -12,6 +12,7 @@ @interface LNLocalization () @property (nonatomic, copy, readwrite) NSString *key; @property (nonatomic, copy, readwrite) NSString *value; +@property (nonatomic, copy, readwrite) NSString *comment; @property (nonatomic, assign, readwrite) NSRange entityRange; @property (nonatomic, assign, readwrite) NSRange keyRange; @@ -23,18 +24,19 @@ @interface LNLocalization () @implementation LNLocalization -+ (instancetype)localizationWithKey:(NSString *)key value:(NSString *)value entityRange:(NSRange)entityRange keyRange:(NSRange)keyRange valueRange:(NSRange)valueRange collection:(LNLocalizationCollection *)collection ++ (instancetype)localizationWithKey:(NSString *)key value:(NSString *)value comment:(NSString *)comment entityRange:(NSRange)entityRange keyRange:(NSRange)keyRange valueRange:(NSRange)valueRange collection:(LNLocalizationCollection *)collection { - return [[self alloc] initWithKey:key value:value entityRange:(NSRange)entityRange keyRange:keyRange valueRange:valueRange collection:collection]; + return [[self alloc] initWithKey:key value:value comment:comment entityRange:(NSRange)entityRange keyRange:keyRange valueRange:valueRange collection:collection]; } -- (instancetype)initWithKey:(NSString *)key value:(NSString *)value entityRange:(NSRange)entityRange keyRange:(NSRange)keyRange valueRange:(NSRange)valueRange collection:(LNLocalizationCollection *)collection +- (instancetype)initWithKey:(NSString *)key value:(NSString *)value comment:(NSString *)comment entityRange:(NSRange)entityRange keyRange:(NSRange)keyRange valueRange:(NSRange)valueRange collection:(LNLocalizationCollection *)collection { self = [super init]; if (self) { self.key = key; self.value = value; + self.comment = comment; self.entityRange = entityRange; self.keyRange = keyRange; @@ -66,11 +68,12 @@ - (NSUInteger)hash - (NSString *)description { return [NSString stringWithFormat: - @"<%@: %p; key = %@; value = %@; entityRange = %@; keyRange = %@; valueRange = %@; collection = %p>", + @"<%@: %p; key = %@; value = %@; comment = %@; entityRange = %@; keyRange = %@; valueRange = %@; collection = %p>", NSStringFromClass([self class]), self, self.key, self.value, + self.comment, NSStringFromRange(self.entityRange), NSStringFromRange(self.keyRange), NSStringFromRange(self.valueRange), diff --git a/Lin/LNLocalizationCollection.m b/Lin/LNLocalizationCollection.m index 2c786d5..64462d1 100644 --- a/Lin/LNLocalizationCollection.m +++ b/Lin/LNLocalizationCollection.m @@ -36,7 +36,10 @@ - (instancetype)initWithContentsOfFile:(NSString *)filePath // Extract language designation NSArray *pathComponents = [filePath pathComponents]; - self.languageDesignation = [[pathComponents objectAtIndex:pathComponents.count - 2] stringByDeletingPathExtension]; + // The superdir is shown in parentheses to distinguish possible duplicate name-language combos + self.languageDesignation = [NSString stringWithFormat:@"%@ (%@)", + [[pathComponents objectAtIndex:pathComponents.count - 2] stringByDeletingPathExtension], + [pathComponents objectAtIndex:pathComponents.count - 3]]; // Update [self reloadLocalizations]; @@ -93,59 +96,44 @@ - (void)reloadLocalizations if (contents) { NSMutableSet *localizations = [NSMutableSet set]; - // Parse - __block NSInteger lineOffset = 0; - __block NSString *key; - __block NSString *value; - __block NSRange entityRange; - __block NSRange keyRange; - __block NSRange valueRange; + // to keep it simple for now comments are only accepted if + // * the comment is C-style + // * the comment is on the line above the key-value + // * the comment is followed by no whitespace except one new line + // * the key-value line is not prefixed with any whitespace + // this can be changed later on when time allows :) + // example found below: + // v - no whitespace here except newline + // /* Comment */ + // @"key" = @"value"; + // ^ - no whitespace here - NSRegularExpression *regularExpression = [NSRegularExpression regularExpressionWithPattern:@"(\"(\\S+.*\\S+)\"|(\\S+.*\\S+))\\s*=\\s*\"(.*)\";$" + NSRegularExpression *regularExpression = [NSRegularExpression regularExpressionWithPattern: + @"(?#comment)(?:/\\*(.*)\\*/\n)?" + @"(?#key )(?:\"(.*)\"|(\\S+))" + @"(?#equals )\\s*=\\s*" + @"(?#value )\"(.*)\";" options:0 - error:NULL]; + error:nil]; - [contents enumerateLinesUsingBlock:^(NSString *line, BOOL *stop) { - key = nil; - value = nil; - keyRange = NSMakeRange(NSNotFound, 0); - valueRange = NSMakeRange(NSNotFound, 0); + [regularExpression enumerateMatchesInString:contents options:0 range:NSMakeRange(0, [contents length]) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) { + NSString *comment = nil; - NSTextCheckingResult *result = [regularExpression firstMatchInString:line - options:0 - range:NSMakeRange(0, line.length)]; + NSRange entityRange = [result rangeAtIndex:0]; + NSRange commentRange = [result rangeAtIndex:1]; + NSRange keyRange = [result rangeAtIndex:2].location != NSNotFound ? [result rangeAtIndex:2] : [result rangeAtIndex:3]; + NSRange valueRange = [result rangeAtIndex:4]; - if (result.range.location != NSNotFound && result.numberOfRanges == 5) { - entityRange = [result rangeAtIndex:0]; - entityRange.location += lineOffset; - - keyRange = [result rangeAtIndex:2]; - if (keyRange.location == NSNotFound) keyRange = [result rangeAtIndex:3]; - - valueRange = [result rangeAtIndex:4]; - - key = [line substringWithRange:keyRange]; - value = [line substringWithRange:valueRange]; - - keyRange.location += lineOffset; - valueRange.location += lineOffset; - } + if (commentRange.location != NSNotFound) + comment = [contents substringWithRange:commentRange]; - // Create localization - if (key != nil && value != nil) { - LNLocalization *localization = [LNLocalization localizationWithKey:key - value:value - entityRange:entityRange - keyRange:keyRange - valueRange:valueRange - collection:self]; - - [localizations addObject:localization]; - } - - // Move offset - NSRange lineRange = [contents lineRangeForRange:NSMakeRange(lineOffset, 0)]; - lineOffset += lineRange.length; + [localizations addObject:[LNLocalization localizationWithKey:[contents substringWithRange:keyRange] + value:[contents substringWithRange:valueRange] + comment:comment + entityRange:entityRange + keyRange:keyRange + valueRange:valueRange + collection:self]]; }]; self.localizations = localizations; @@ -154,70 +142,79 @@ - (void)reloadLocalizations } } -- (void)addLocalization:(LNLocalization *)localization +- (NSString *)formatEntity:(LNLocalization *)localization { - // Load contents - NSString *contents = [self loadContentsOfFile:self.filePath]; + NSString *comment = @""; - // Add - if (![contents hasSuffix:@"\n"]) { - contents = [contents stringByAppendingString:@"\n"]; + if (localization.comment && [localization.comment length] > 0) { + // Add spaces at the ends if needed + NSString *prefix = [localization.comment hasPrefix:@" "] ? @"" : @" "; + NSString *suffix = [localization.comment hasSuffix:@" "] ? @"" : @" "; + + comment = [NSString stringWithFormat:@"/*%@%@%@*/\n", prefix, localization.comment, suffix]; } - contents = [contents stringByAppendingFormat:@"\"%@\" = \"%@\";\n", localization.key, localization.value]; - + return [NSString stringWithFormat:@"%@\"%@\" = \"%@\";", comment, localization.key, localization.value]; +} + +- (void)writeContents:(NSString *)contents withRange:(NSRange)range replacedWithString:(NSString *)string +{ // Override NSError *error = nil; - [contents writeToFile:self.filePath atomically:NO encoding:NSUTF8StringEncoding error:&error]; + [[contents stringByReplacingCharactersInRange:range withString:string] writeToFile:self.filePath atomically:NO encoding:NSUTF8StringEncoding error:&error]; if (error) { NSLog(@"Error: %@", [error localizedDescription]); } - + // Reload [self reloadLocalizations]; } -- (void)deleteLocalization:(LNLocalization *)localization +- (void)addLocalization:(LNLocalization *)localization { // Load contents NSString *contents = [self loadContentsOfFile:self.filePath]; + __block NSRange range = NSMakeRange([contents length], 0); // Starting point if no trailing white space found - // Delete line - NSRange lineRange = [contents lineRangeForRange:localization.entityRange]; - contents = [contents stringByReplacingCharactersInRange:lineRange withString:@""]; + NSRegularExpression *regularExpression = [NSRegularExpression regularExpressionWithPattern: + @"(?#capture trailing whitespace)(\\s+)$" + options:0 + error:nil]; - // Override - NSError *error = nil; - [contents writeToFile:self.filePath atomically:NO encoding:NSUTF8StringEncoding error:&error]; + [regularExpression enumerateMatchesInString:contents options:0 range:NSMakeRange(0, [contents length]) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) { + range = [result rangeAtIndex:1]; + }]; - if (error) { - NSLog(@"Error: %@", [error localizedDescription]); - } - - // Reload - [self reloadLocalizations]; + [self writeContents:contents withRange:range replacedWithString:[NSString stringWithFormat:@"\n\n%@\n", [self formatEntity:localization]]]; } -- (void)replaceLocalization:(LNLocalization *)localization withLocalization:(LNLocalization *)newLocalization +- (void)deleteLocalization:(LNLocalization *)localization { + NSCharacterSet *whitespace = [NSCharacterSet whitespaceAndNewlineCharacterSet]; + // Load contents NSString *contents = [self loadContentsOfFile:self.filePath]; - // Replace - NSString *newEntity = [NSString stringWithFormat:@"\"%@\" = \"%@\";", newLocalization.key, newLocalization.value]; - contents = [contents stringByReplacingCharactersInRange:localization.entityRange withString:newEntity]; + NSRange range = localization.entityRange; // Starting point - // Override - NSError *error = nil; - [contents writeToFile:self.filePath atomically:NO encoding:NSUTF8StringEncoding error:&error]; + // Expand range left if whitespace is present + while (range.location > 0 && [whitespace characterIsMember:[contents characterAtIndex:range.location - 1]]) { + range.location--; + range.length++; + } - if (error) { - NSLog(@"Error: %@", [error localizedDescription]); + // Expand range right if whitespace is present + while (NSMaxRange(range) < [contents length] && [whitespace characterIsMember:[contents characterAtIndex:NSMaxRange(range)]]) { + range.length++; } - // Reload - [self reloadLocalizations]; + [self writeContents:[self loadContentsOfFile:self.filePath] withRange:range replacedWithString:@"\n\n"]; +} + +- (void)replaceLocalization:(LNLocalization *)localization withLocalization:(LNLocalization *)newLocalization +{ + [self writeContents:[self loadContentsOfFile:self.filePath] withRange:localization.entityRange replacedWithString:[self formatEntity:newLocalization]]; } @end diff --git a/Lin/LNPopoverContentView.m b/Lin/LNPopoverContentView.m index 65f6129..5cb2165 100644 --- a/Lin/LNPopoverContentView.m +++ b/Lin/LNPopoverContentView.m @@ -110,6 +110,7 @@ - (void)textDidEndEditing:(NSNotification *)notification NSString *key = localization.key; NSString *value = localization.value; + NSString *comment = localization.comment; NSString *columnIdentifier = [self.tableView editedColumnIdentifier]; @@ -117,10 +118,13 @@ - (void)textDidEndEditing:(NSNotification *)notification key = textView.textStorage.string; } else if ([columnIdentifier isEqualToString:@"value"]) { value = textView.textStorage.string; + } else if ([columnIdentifier isEqualToString:@"comment"]) { + comment = textView.textStorage.string; } - + LNLocalization *newLocalization = [LNLocalization localizationWithKey:key value:value + comment:comment entityRange:localization.entityRange keyRange:localization.keyRange valueRange:localization.valueRange @@ -184,9 +188,11 @@ - (IBAction)addLocalization:(id)sender LNLocalizationCollection *collection = accessoryView.selectedCollection; NSString *key = accessoryView.inputtedKey; NSString *value = accessoryView.inputtedValue; + NSString *comment = accessoryView.inputtedComment; LNLocalization *localization = [LNLocalization localizationWithKey:key value:value + comment:comment entityRange:NSMakeRange(NSNotFound, 0) keyRange:NSMakeRange(NSNotFound, 0) valueRange:NSMakeRange(NSNotFound, 0) @@ -309,6 +315,9 @@ - (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColum else if ([identifier isEqualToString:@"value"]) { return localization.value; } + else if ([identifier isEqualToString:@"comment"]) { + return localization.comment; + } return nil; } diff --git a/Lin/LNPopoverContentView.xib b/Lin/LNPopoverContentView.xib index aeaa319..a6b5e8b 100644 --- a/Lin/LNPopoverContentView.xib +++ b/Lin/LNPopoverContentView.xib @@ -1,8 +1,8 @@ - + - + @@ -12,19 +12,19 @@ - - + + - + - + - + @@ -87,6 +87,20 @@ + + + + + + + + + + + + + + @@ -97,7 +111,7 @@ - + - + - + - +