diff --git a/AppKit/CPRuleEditor/_CPRuleEditorLocalizer.j b/AppKit/CPRuleEditor/_CPRuleEditorLocalizer.j index db08bd739..f9dddbfb9 100644 --- a/AppKit/CPRuleEditor/_CPRuleEditorLocalizer.j +++ b/AppKit/CPRuleEditor/_CPRuleEditorLocalizer.j @@ -94,9 +94,8 @@ var LocalizerStringsRegex = new RegExp("\"(.+)\"\\s*=\\s*\"(.+)\"\\s*;\\s*(//.+) return aString; } -#pragma mark - Whole Sentence Formatting & Reordering Helpers +#pragma mark - Formatting & Reordering Helpers -// Constructs the English-matching format representation of a single view - (CPString)_englishRepresentationForView:(id)aView { if ([aView isKindOfClass:[CPPopUpButton class]]) @@ -104,14 +103,32 @@ var LocalizerStringsRegex = new RegExp("\"(.+)\"\\s*=\\s*\"(.+)\"\\s*;\\s*(//.+) var selectedItem = [aView selectedItem]; if (selectedItem) { - var originalTitle = [selectedItem representedObject] || selectedItem._originalTitle || [selectedItem title]; + var originalTitle = selectedItem._originalTitle; + + // Fallback: If not cached directly, inspect representedObject payload dictionary + if (!originalTitle) + { + var rep = [selectedItem representedObject]; + if (rep && typeof rep === "object" && typeof rep.objectForKey === "function") + { + originalTitle = [rep objectForKey:@"value"]; + } + else if (rep && typeof rep === "string") + { + originalTitle = rep; + } + } + if (!originalTitle) + { + originalTitle = [selectedItem title]; + } return "%[" + originalTitle + "]@"; } return "%[]@"; } else if ([aView isKindOfClass:[CPTextField class]] && ![aView isEditable]) { - return [aView stringValue]; + return aView._originalText || [aView stringValue]; } else { @@ -119,7 +136,6 @@ var LocalizerStringsRegex = new RegExp("\"(.+)\"\\s*=\\s*\"(.+)\"\\s*;\\s*(//.+) } } -// Builds the current formatting lookup key (e.g. "%[property]@ %[is]@ %@") - (CPString)formattingKeyForViews:(CPArray)views { var keyParts = []; @@ -132,7 +148,6 @@ var LocalizerStringsRegex = new RegExp("\"(.+)\"\\s*=\\s*\"(.+)\"\\s*;\\s*(//.+) return [keyParts componentsJoinedByString:@" "]; } -// Localizes all popup menu options within the row under their proper context - (void)localizeMenuItemsForViews:(CPArray)views { var count = [views count]; @@ -151,10 +166,18 @@ var LocalizerStringsRegex = new RegExp("\"(.+)\"\\s*=\\s*\"(.+)\"\\s*;\\s*(//.+) if (!item._originalTitle) { - item._originalTitle = [item title]; + var rep = [item representedObject]; + if (rep && typeof rep === "object" && typeof rep.objectForKey === "function") + { + item._originalTitle = [rep objectForKey:@"value"]; + } + else + { + item._originalTitle = [item title]; + } } - // Temporarily select item to formulate the unique localization key + // Temporarily select item to generate formatting key context [view selectItem:item]; var tempKey = [self formattingKeyForViews:views]; @@ -162,7 +185,6 @@ var LocalizerStringsRegex = new RegExp("\"(.+)\"\\s*=\\s*\"(.+)\"\\s*;\\s*(//.+) if (tempPattern !== tempKey) { - // Search for this view position's translation inside the pattern (e.g. %2$[son iguales]@) var regex = /%(\d+)\$(?:\[([^\]]+)\])?@/g; var match; while ((match = regex.exec(tempPattern)) !== null) @@ -179,7 +201,6 @@ var LocalizerStringsRegex = new RegExp("\"(.+)\"\\s*=\\s*\"(.+)\"\\s*;\\s*(//.+) } } - // Restore initial selection if (selectedItem) { [view selectItem:selectedItem]; @@ -188,7 +209,6 @@ var LocalizerStringsRegex = new RegExp("\"(.+)\"\\s*=\\s*\"(.+)\"\\s*;\\s*(//.+) } } -// Translates labels and reorders the active subviews based on positional formatting string - (CPArray)localizeAndReorderViews:(CPArray)views { var key = [self formattingKeyForViews:views]; @@ -200,23 +220,19 @@ var LocalizerStringsRegex = new RegExp("\"(.+)\"\\s*=\\s*\"(.+)\"\\s*;\\s*(//.+) } var newViews = [CPMutableArray array]; - - // Pattern to extract index and translation: %1$[translation]@ or %1$@ var regex = /%(\d+)\$(?:\[([^\]]+)\])?@/g; var lastIndex = 0; var match; while ((match = regex.exec(localizedPattern)) !== null) { - // 1. Insert any leading static text (e.g. " y ") var literalText = localizedPattern.substring(lastIndex, match.index); if (literalText.length > 0) { - var label = [CPTextField labelWithString:literalText]; + var label = [CPTextField labelWithTitle:literalText]; [newViews addObject:label]; } - // 2. Identify the original view corresponding to the positional index var position = parseInt(match[1], 10) - 1; var translatedValue = match[2]; @@ -250,13 +266,12 @@ var LocalizerStringsRegex = new RegExp("\"(.+)\"\\s*=\\s*\"(.+)\"\\s*;\\s*(//.+) lastIndex = regex.lastIndex; } - // 3. Append trailing static text if (lastIndex < localizedPattern.length) { var literalText = localizedPattern.substring(lastIndex); if (literalText.length > 0) { - var label = [CPTextField labelWithString:literalText]; + var label = [CPTextField labelWithTitle:literalText]; [newViews addObject:label]; } } diff --git a/AppKit/CPRuleEditor/_CPRuleEditorViewSliceRow.j b/AppKit/CPRuleEditor/_CPRuleEditorViewSliceRow.j index 181f935ba..a32764fbe 100644 --- a/AppKit/CPRuleEditor/_CPRuleEditorViewSliceRow.j +++ b/AppKit/CPRuleEditor/_CPRuleEditorViewSliceRow.j @@ -114,8 +114,32 @@ - (CPMenuItem)_createMenuItemWithTitle:(CPString )title { - title = [[_ruleEditor standardLocalizer] localizedStringForString:title]; - return [[CPMenuItem alloc] initWithTitle:title action:nil keyEquivalent:@""]; + var originalTitle = title; + var localizedTitle = [[_ruleEditor standardLocalizer] localizedStringForString:title]; + var item = [[CPMenuItem alloc] initWithTitle:localizedTitle action:nil keyEquivalent:@""]; + + // Cache the raw English title for pattern-matching + item._originalTitle = originalTitle; + return item; +} + +- (CPTextField)_createStaticTextFieldWithStringValue:(CPString)text +{ + var textField = [[CPTextField alloc] initWithFrame:CGRectMakeZero()], + ruleEditorFont = [_ruleEditor font], + font = [CPFont fontWithName:[ruleEditorFont familyName] size:[ruleEditorFont size] + 2], + localizedText = [[_ruleEditor standardLocalizer] localizedStringForString:text], + size = [localizedText sizeWithFont:font]; + + [textField setFrameSize:CGSizeMake(size.width + 4, [_ruleEditor rowHeight])]; + [textField setValue:font forThemeAttribute:@"font"]; + [textField setValue:[_ruleEditor _verticalAlignment] forThemeAttribute:@"vertical-alignment"]; + [textField setStringValue:localizedText]; + + // Cache the raw English text for pattern-matching + textField._originalText = text; + + return textField; } - (CPPopUpButton)_createPopUpButtonWithItems:(CPArray)itemsArray selectedItemIndex:(int)index @@ -142,22 +166,6 @@ return [CPMenuItem separatorItem]; } -- (CPTextField)_createStaticTextFieldWithStringValue:(CPString)text -{ - var textField = [[CPTextField alloc] initWithFrame:CGRectMakeZero()], - ruleEditorFont = [_ruleEditor font], - font = [CPFont fontWithName:[ruleEditorFont familyName] size:[ruleEditorFont size] + 2], - localizedText = [[_ruleEditor standardLocalizer] localizedStringForString:text], - size = [localizedText sizeWithFont:font]; - - [textField setFrameSize:CGSizeMake(size.width + 4, [_ruleEditor rowHeight])]; - [textField setValue:font forThemeAttribute:@"font"]; - [textField setValue:[_ruleEditor _verticalAlignment] forThemeAttribute:@"vertical-alignment"]; - [textField setStringValue:localizedText]; - - return textField; -} - - (void)_addOption:(id)sender { if (_rowIndex == [_ruleEditor numberOfRows] - 1) diff --git a/Tests/Manual/CPRuleEditorTestSpanish/AppController.j b/Tests/Manual/CPRuleEditorTestSpanish/AppController.j new file mode 100644 index 000000000..e81076955 --- /dev/null +++ b/Tests/Manual/CPRuleEditorTestSpanish/AppController.j @@ -0,0 +1,92 @@ +@import +@import +@import +@import +@import +@import "RuleDelegate.j" + +@implementation AppController : CPObject +{ + CPWindow theWindow; + CPRuleEditor ruleEditor; + CPTextField predicateField; + RuleDelegate ruleDelegate; +} + +- (void)applicationDidFinishLaunching:(CPNotification)aNotification +{ + theWindow = [[CPWindow alloc] initWithContentRect:CGRectMake(50, 50, 800, 500) + styleMask:CPTitledWindowMask | CPClosableWindowMask | CPResizableWindowMask]; + [theWindow setTitle:@"Spanish CPRuleEditor Whole-Sentence Localization Test"]; + [theWindow setFullPlatformWindow:YES]; + + var contentView = [theWindow contentView]; + [contentView setBackgroundColor:[CPColor colorWithHexString:@"f3f4f5"]]; + + var label = [CPTextField labelWithTitle:@"CPRuleEditor Sentence Localization & Positional Reordering:"]; + [label setFrame:CGRectMake(20, 20, 760, 24)]; + [label setFont:[CPFont boldSystemFontOfSize:14]]; + [contentView addSubview:label]; + + ruleDelegate = [[RuleDelegate alloc] init]; + + // Create Rule Editor + ruleEditor = [[CPRuleEditor alloc] initWithFrame:CGRectMake(20, 55, 760, 250)]; + [ruleEditor setAutoresizingMask:CPViewWidthSizable]; + [ruleEditor setDelegate:ruleDelegate]; + [ruleEditor setEditable:YES]; + [ruleEditor setRowHeight:28]; + [ruleEditor setTarget:self]; + [ruleEditor setAction:@selector(ruleEditorAction:)]; + + // Populate Spanish translations programmatically + var path = [[CPBundle mainBundle] pathForResource:@"Spanish.strings"]; + if (path) + { + [[ruleEditor standardLocalizer] loadContentOfURL:[CPURL URLWithString:path]]; + } + + [contentView addSubview:ruleEditor]; + + // Populate default row + [ruleEditor addRow:self]; + + // Display Output Label + var predLabel = [CPTextField labelWithTitle:@"CPRuleEditor Sentence Localization & Positional Reordering:"]; + [predLabel setFrame:CGRectMake(20, 320, 760, 20)]; + [predLabel setFont:[CPFont boldSystemFontOfSize:12]]; + [contentView addSubview:predLabel]; + + // Predicate string value display + predicateField = [[CPTextField alloc] initWithFrame:CGRectMake(20, 345, 760, 36)]; + [predicateField setAutoresizingMask:CPViewWidthSizable]; + [predicateField setBezeled:YES]; + [predicateField setEditable:NO]; + [predicateField setStringValue:@""]; + [predicateField setFont:[CPFont systemFontOfSize:13]]; + [contentView addSubview:predicateField]; + + var addBtn = [CPButton buttonWithTitle:@"Añadir regla"]; + [addBtn setFrame:CGRectMake(20, 400, 120, 24)]; + [addBtn setTarget:ruleEditor]; + [addBtn setAction:@selector(addRow:)]; + [contentView addSubview:addBtn]; + + [theWindow orderFront:self]; + [self ruleEditorAction:nil]; +} + +- (void)ruleEditorAction:(id)sender +{ + var predicate = [ruleEditor predicate]; + if (predicate) + { + [predicateField setStringValue:[predicate predicateFormat]]; + } + else + { + [predicateField setStringValue:@"(No predicate evaluated)"]; + } +} + +@end diff --git a/Tests/Manual/CPRuleEditorTestSpanish/Info.plist b/Tests/Manual/CPRuleEditorTestSpanish/Info.plist new file mode 100644 index 000000000..68f9e7d32 --- /dev/null +++ b/Tests/Manual/CPRuleEditorTestSpanish/Info.plist @@ -0,0 +1,12 @@ + + + + + CPApplicationDelegateClass + AppController + CPBundleName + CPTextViewTest + CPPrincipalClass + CPApplication + + diff --git a/Tests/Manual/CPRuleEditorTestSpanish/Jakefile b/Tests/Manual/CPRuleEditorTestSpanish/Jakefile new file mode 100644 index 000000000..c66879a92 --- /dev/null +++ b/Tests/Manual/CPRuleEditorTestSpanish/Jakefile @@ -0,0 +1,94 @@ +/* + * Jakefile + * CPRuleEditorCibTest + * + * Created by You on September 3, 2010. + * Copyright 2010, Your Company All rights reserved. + */ + +var ENV = require("system").env, + FILE = require("file"), + JAKE = require("jake"), + task = JAKE.task, + FileList = JAKE.FileList, + app = require("cappuccino/jake").app, + configuration = ENV["CONFIG"] || ENV["CONFIGURATION"] || ENV["c"] || "Debug", + OS = require("os"); + +app ("CPRuleEditorCibTest", function(task) +{ + task.setBuildIntermediatesPath(FILE.join("Build", "CPRuleEditorCibTest.build", configuration)); + task.setBuildPath(FILE.join("Build", configuration)); + + task.setProductName("CPRuleEditorCibTest"); + task.setIdentifier("com.yourcompany.CPRuleEditorCibTest"); + task.setVersion("1.0"); + task.setAuthor("Your Company"); + task.setEmail("feedback @nospam@ yourcompany.com"); + task.setSummary("CPRuleEditorCibTest"); + task.setSources((new FileList("**/*.j")).exclude(FILE.join("Build", "**"))); + task.setResources(new FileList("Resources/**")); + task.setIndexFilePath("index.html"); + task.setInfoPlistPath("Info.plist"); + task.setNib2CibFlags("-R Resources/"); + + if (configuration === "Debug") + task.setCompilerFlags("-DDEBUG -g"); + else + task.setCompilerFlags("-O"); +}); + +task ("default", ["CPRuleEditorCibTest"], function() +{ + printResults(configuration); +}); + +task ("build", ["default"]); + +task ("debug", function() +{ + ENV["CONFIGURATION"] = "Debug"; + JAKE.subjake(["."], "build", ENV); +}); + +task ("release", function() +{ + ENV["CONFIGURATION"] = "Release"; + JAKE.subjake(["."], "build", ENV); +}); + +task ("run", ["debug"], function() +{ + OS.system(["open", FILE.join("Build", "Debug", "CPRuleEditorCibTest", "index.html")]); +}); + +task ("run-release", ["release"], function() +{ + OS.system(["open", FILE.join("Build", "Release", "CPRuleEditorCibTest", "index.html")]); +}); + +task ("deploy", ["release"], function() +{ + FILE.mkdirs(FILE.join("Build", "Deployment", "CPRuleEditorCibTest")); + OS.system(["press", "-f", FILE.join("Build", "Release", "CPRuleEditorCibTest"), FILE.join("Build", "Deployment", "CPRuleEditorCibTest")]); + printResults("Deployment") +}); + +task ("desktop", ["release"], function() +{ + FILE.mkdirs(FILE.join("Build", "Desktop", "CPRuleEditorCibTest")); + require("cappuccino/nativehost").buildNativeHost(FILE.join("Build", "Release", "CPRuleEditorCibTest"), FILE.join("Build", "Desktop", "CPRuleEditorCibTest", "CPRuleEditorCibTest.app")); + printResults("Desktop") +}); + +task ("run-desktop", ["desktop"], function() +{ + OS.system([FILE.join("Build", "Desktop", "CPRuleEditorCibTest", "CPRuleEditorCibTest.app", "Contents", "MacOS", "NativeHost"), "-i"]); +}); + +function printResults(configuration) +{ + print("----------------------------"); + print(configuration+" app built at path: "+FILE.join("Build", configuration, "CPRuleEditorCibTest")); + print("----------------------------"); +} diff --git a/Tests/Manual/CPRuleEditorTestSpanish/Resources/Spanish.strings b/Tests/Manual/CPRuleEditorTestSpanish/Resources/Spanish.strings new file mode 100644 index 000000000..17a1738c9 --- /dev/null +++ b/Tests/Manual/CPRuleEditorTestSpanish/Resources/Spanish.strings @@ -0,0 +1,13 @@ +/* Spanish formatting patterns for CPRuleEditor */ + +"%[firstName]@ %[is equal to]@ %@" = "%1$[Nombre]@ y %3$@ %2$[son iguales]@"; +"%[firstName]@ %[contains]@ %@" = "%1$[Nombre]@ %2$[contiene]@ %3$@"; + +"%[lastName]@ %[is equal to]@ %@" = "%1$[Apellido]@ y %3$@ %2$[son iguales]@"; +"%[lastName]@ %[contains]@ %@" = "%1$[Apellido]@ %2$[contiene]@ %3$@"; + +"%[age]@ %[is equal to]@ %@" = "%1$[Edad]@ y %3$@ %2$[son iguales]@"; + +"Add row" = "Añadir regla"; +"Delete row" = "Eliminar regla"; +"Add compound row" = "Añadir grupo de reglas"; \ No newline at end of file diff --git a/Tests/Manual/CPRuleEditorTestSpanish/Resources/spinner.gif b/Tests/Manual/CPRuleEditorTestSpanish/Resources/spinner.gif new file mode 100644 index 000000000..a5e705f6c Binary files /dev/null and b/Tests/Manual/CPRuleEditorTestSpanish/Resources/spinner.gif differ diff --git a/Tests/Manual/CPRuleEditorTestSpanish/RuleDelegate.j b/Tests/Manual/CPRuleEditorTestSpanish/RuleDelegate.j new file mode 100644 index 000000000..1c536b7c6 --- /dev/null +++ b/Tests/Manual/CPRuleEditorTestSpanish/RuleDelegate.j @@ -0,0 +1,113 @@ +@import +@import +@import + +@implementation RuleDelegate : CPObject +{ +} + +// 1. Root criteria and children +- (id)ruleEditor:(CPRuleEditor)editor child:(CPInteger)index forCriterion:(id)criterion withRowType:(CPRuleEditorRowType)rowType +{ + if (criterion == nil) + { + // Root criteria + return [@[@"firstName", @"lastName", @"age"] objectAtIndex:index]; + } + + if ([criterion isEqualToString:@"firstName"] || [criterion isEqualToString:@"lastName"]) + { + return [@[@"contains", @"is equal to"] objectAtIndex:index]; + } + + if ([criterion isEqualToString:@"age"]) + { + return [@[@"is equal to"] objectAtIndex:index]; + } + + if ([criterion isEqualToString:@"contains"] || [criterion isEqualToString:@"is equal to"]) + { + // The child of an operator is the leaf value node + return @"value"; + } + + return nil; +} + +// 2. Number of children for a given criterion +- (CPInteger)ruleEditor:(CPRuleEditor)editor numberOfChildrenForCriterion:(id)criterion withRowType:(CPRuleEditorRowType)rowType +{ + if (criterion == nil) + { + return 3; // firstName, lastName, age + } + + if ([criterion isEqualToString:@"firstName"] || [criterion isEqualToString:@"lastName"]) + { + return 2; // contains, is equal to + } + + if ([criterion isEqualToString:@"age"]) + { + return 1; // is equal to + } + + if ([criterion isEqualToString:@"contains"] || [criterion isEqualToString:@"is equal to"]) + { + // Operators have one child representing the input field value + return 1; + } + + return 0; // Leaf nodes return 0 +} + +// 3. Display values (labels, popup titles, or text input fields) +- (id)ruleEditor:(CPRuleEditor)editor displayValueForCriterion:(id)criterion inRow:(CPInteger)row +{ + if ([criterion isEqualToString:@"firstName"]) return @"firstName"; + if ([criterion isEqualToString:@"lastName"]) return @"lastName"; + if ([criterion isEqualToString:@"age"]) return @"age"; + + if ([criterion isEqualToString:@"contains"]) return @"contains"; + if ([criterion isEqualToString:@"is equal to"]) return @"is equal to"; + + if ([criterion isEqualToString:@"value"]) + { + // Return the actual editable text field view for the leaf node + var textField = [[CPTextField alloc] initWithFrame:CGRectMake(0, 0, 120, 24)]; + [textField setBezeled:YES]; + [textField setBezelStyle:CPTextFieldSquareBezel]; + [textField setEditable:YES]; + [textField setStringValue:@""]; + return textField; + } + + return nil; +} + +// 4. Predicate parts mapping +- (CPDictionary)ruleEditor:(CPRuleEditor)editor predicatePartsForCriterion:(id)criterion withDisplayValue:(id)value inRow:(CPInteger)row +{ + var parts = @{}; + + if ([criterion isEqualToString:@"firstName"] || [criterion isEqualToString:@"lastName"] || [criterion isEqualToString:@"age"]) + { + [parts setObject:[CPExpression expressionForKeyPath:criterion] forKey:CPRuleEditorPredicateLeftExpression]; + } + else if ([criterion isEqualToString:@"contains"]) + { + [parts setObject:[CPNumber numberWithUnsignedInt:CPContainsPredicateOperatorType] forKey:CPRuleEditorPredicateOperatorType]; + } + else if ([criterion isEqualToString:@"is equal to"]) + { + [parts setObject:[CPNumber numberWithUnsignedInt:CPEqualToPredicateOperatorType] forKey:CPRuleEditorPredicateOperatorType]; + } + else if ([criterion isEqualToString:@"value"]) + { + [parts setObject:[CPExpression expressionForConstantValue:[value stringValue]] forKey:CPRuleEditorPredicateRightExpression]; + } + + return parts; +} + +@end diff --git a/Tests/Manual/CPRuleEditorTestSpanish/index-debug.html b/Tests/Manual/CPRuleEditorTestSpanish/index-debug.html new file mode 100644 index 000000000..a36b1d3b9 --- /dev/null +++ b/Tests/Manual/CPRuleEditorTestSpanish/index-debug.html @@ -0,0 +1,204 @@ + + + + + + + + + + + + + + + + + + __project.name__ + + + + + + + + + + + + + + +
+
+
+ +
+
+ +
+ + diff --git a/Tests/Manual/CPRuleEditorTestSpanish/index.html b/Tests/Manual/CPRuleEditorTestSpanish/index.html new file mode 100644 index 000000000..ac42c98a7 --- /dev/null +++ b/Tests/Manual/CPRuleEditorTestSpanish/index.html @@ -0,0 +1,166 @@ + + + + + + + + + + + + + + + + + + __project.name__ + + + + + + + + + + + + +
+
+
+ +
+
+ +
+ + diff --git a/Tests/Manual/CPRuleEditorTestSpanish/main.j b/Tests/Manual/CPRuleEditorTestSpanish/main.j new file mode 100644 index 000000000..7424228fa --- /dev/null +++ b/Tests/Manual/CPRuleEditorTestSpanish/main.j @@ -0,0 +1,18 @@ +/* + * AppController.j + * CPRuleEditorCibTest + * + * Created by You on September 3, 2010. + * Copyright 2010, Your Company All rights reserved. + */ + +@import +@import + +@import "AppController.j" + + +function main(args, namedArgs) +{ + CPApplicationMain(args, namedArgs); +}