Why is NSPredicateEditor automatically localizing some Expression strings? How to disable?

304 views Asked by At

I'm finding that some NSPredicateEditor / NSPredicateEditorRowTemplate values are being localized automatically by OS X.

This is easily observable in the mapping of operators: .equalTo to the string is.

But I just noticed that UTI strings are being mapped to a human-readable version of that UTI.

Below, I set "public.image" but it is then displayed as "image".

UTI operators in NSPredicateEditor

func setupPredicateEditor() {
    let left = [NSExpression(forKeyPath: "Type")]

    let operators = [NSComparisonPredicate.Operator.equalTo.rawValue as NSNumber]

    let right = [
        NSExpression(forConstantValue: "public.image"),
        NSExpression(forConstantValue: "public.text"),
        NSExpression(forConstantValue: "public.fakeUTI"),
        ];

    let rowTemplate = NSPredicateEditorRowTemplate.init(leftExpressions: left, rightExpressions: right, modifier: .all, operators: operators, options: 0)
    predicateEditor.rowTemplates = [rowTemplate]

    predicateEditor.addRow(self)
}

Only certain valid UTIs are mapped in this manner. I do not know if other formats beyond Operators and UTIs are given automatic localized strings.

This is an issue because I want to localize my NSPredicateEditor and Row Templates.

The process for localizing the predicate involves matching a key to a localized value.

KEY: "%[left]@ %[is]@ %[right]@"
VAL: "%1$[left_display_string]@ %2$[is]@ %3$[right_display_string]@"

The problem is that the values in the key must match the strings shown in the UI. Not the strings originally set to the Row Template's left and right expressions.

So, I cannot localize using "public.image" in the key. The UI is for some reason already localizing this to "image". If I want to localize the Row Template, I must use the string "image" instead. And I don't know how or why this "image" string is being picked.

I could determine these strings via testing, then write a table that maps the expression to the localized string. But I'd prefer to have a solution that disables this automatic localization so that I don't have to worry about strings I have not tested.

Why are UTIs being localized automatically? Do other values receive the same treatment?

Is there a way to disable the automatic localization of the UTI strings and/or the entire predicate editor?

3

There are 3 answers

4
Willeke On BEST ANSWER

Not an answer but some info: NSPredicateEditorRowTemplate translates the strings in _displayValueForConstantValue:. It uses a dictionary:

{
    "com.apple.application" = application;
    "com.apple.pict" = PICT;
    "com.apple.protected-mpeg-4-audio" = "purchased music";
    "com.apple.rtfd" = RTFD;
    "com.compuserve.gif" = GIF;
    "com.microsoft.bmp" = BMP;
    "public.audio" = music;
    "public.folder" = folder;
    "public.html" = HTML;
    "public.image" = image;
    "public.jpeg" = JPEG;
    "public.jpeg-2000" = "JPEG 2000";
    "public.mp3" = MP3;
    "public.mpeg-4-audio" = "MPEG4 audio";
    "public.png" = PNG;
    "public.rtf" = RTF;
    "public.source-code" = "source code";
    "public.text" = text;
    "public.tiff" = TIFF;
    "public.xml" = XML;
}

bt:

AppKit`-[NSPredicateEditorRowTemplate _displayValueForConstantValue:] + 913
AppKit`-[NSPredicateEditorRowTemplate _viewFromExpressions:] + 589
AppKit`-[NSPredicateEditorRowTemplate initWithLeftExpressions:rightExpressions:modifier:operators:options:] + 205

Edit:

_displayValueForKeyPath: uses this dictionary:

{
    kMDItemContentCreationDate = Created;
    kMDItemContentModificationDate = "Last modified";
    kMDItemContentTypeTree = "File type";
    kMDItemDisplayName = "File display name";
    kMDItemFSContentChangeDate = "Change date";
    kMDItemFSCreationDate = "Creation date";
    kMDItemFSName = "File name";
    kMDItemFSOwnerGroupID = "Owner group id";
    kMDItemFSOwnerUserID = "Owner user ID";
    kMDItemFSSize = "File size";
    kMDItemLastUsedDate = "Last opened";
    kMDItemPath = "File path";
}

_displayValueForPredicateOperator: and _displayValueForCompoundPredicateType: translate the enums to strings.

This should be documented but it isn't. I put a breakpoint on _displayValueForConstantValue: and I watched what happened. The dictionaries are hard coded and can be different in others versions of macOS.

0
pkamb On

You can call the private API methods directly to use the system's mapping.

In Swift this requires a bridging header:

#import <AppKit/AppKit.h>

@interface NSPredicateEditorRowTemplate ()

- (NSString *)_displayValueForKeyPath:(CFStringRef)keyPath;
- (NSString *)_displayValueForConstantValue:(CFStringRef)constantValue;
- (NSString *)_displayValueForCompoundPredicateType:(NSCompoundPredicateType)compoundPredicateType;
- (NSString *)_displayValueForPredicateOperator:(NSPredicateOperatorType)predicateOperator;

@end

These private methods can then be called like so:

let keyPath = rowTemplate._displayValue(forKeyPath: kMDItemFSSize) // "File size"
let constantValue = rowTemplate._displayValue(forConstantValue: "com.apple.application" as CFString) // "application"
let compoundPredicateType = rowTemplate._displayValue(for: .or) // "Any"

The three above work, but I'm currently having issues with _displayValueForPredicateOperator: crashing with EXC_BAD_ACCESS or returning the wrong string translation.

0
pkamb On

Expanding Willeke's great answer:

Add symbolic breakpoints to the following private methods to see the mappings.

  • _displayValueForKeyPath:
  • _displayValueForConstantValue:
  • _displayValueForCompoundPredicateType:
  • _displayValueForPredicateOperator:

When you breaks, put breakpoint on a line containing objc_msgSend and break again there.

Then input po $rax to print the mapping table.

In the lower two cases it does not use a dictionary, but the strings can be seen when the breakpoints are hit.

_displayValueForKeyPath:

{
    kMDItemContentCreationDate = Created;
    kMDItemContentModificationDate = "Last modified";
    kMDItemContentTypeTree = "File type";
    kMDItemDisplayName = "File display name";
    kMDItemFSContentChangeDate = "Change date";
    kMDItemFSCreationDate = "Creation date";
    kMDItemFSName = "File name";
    kMDItemFSOwnerGroupID = "Owner group id";
    kMDItemFSOwnerUserID = "Owner user ID";
    kMDItemFSSize = "File size";
    kMDItemLastUsedDate = "Last opened";
    kMDItemPath = "File path";
}

_displayValueForConstantValue:

{
    "com.apple.application" = application;
    "com.apple.pict" = PICT;
    "com.apple.protected-mpeg-4-audio" = "purchased music";
    "com.apple.rtfd" = RTFD;
    "com.compuserve.gif" = GIF;
    "com.microsoft.bmp" = BMP;
    "public.audio" = music;
    "public.folder" = folder;
    "public.html" = HTML;
    "public.image" = image;
    "public.jpeg" = JPEG;
    "public.jpeg-2000" = "JPEG 2000";
    "public.mp3" = MP3;
    "public.mpeg-4-audio" = "MPEG4 audio";
    "public.png" = PNG;
    "public.rtf" = RTF;
    "public.source-code" = "source code";
    "public.text" = text;
    "public.tiff" = TIFF;
    "public.xml" = XML;
}

_displayValueForCompoundPredicateType:

0x7fff32a7342c <+20>: leaq   0x5ca25255(%rip), %rax    ; @"Any"
0x7fff32a73434 <+28>: leaq   0x5c9c8dad(%rip), %rax    ; @"None"
0x7fff32a7343c <+36>: leaq   0x5ca00ac5(%rip), %rax    ; @"All"
0x7fff32a73456 <+62>: leaq   0x5ca2524b(%rip), %rdx    ; @"(unknown compound type %ld)"

_displayValueForPredicateOperator:

0x7fff32a7331b <+42>:  leaq   0x5ca251c6(%rip), %rax    ; @"is less than"
0x7fff32a73332 <+65>:  leaq   0x5ca251cf(%rip), %rax    ; @"is less than or equal to"
0x7fff32a7334a <+89>:  leaq   0x5ca252f7(%rip), %rax    ; @"between"
0x7fff32a73356 <+101>: leaq   0x5ca251cb(%rip), %rax    ; @"is greater than"
0x7fff32a73362 <+113>: leaq   0x5ca251df(%rip), %rax    ; @"is greater than or equal to"
0x7fff32a7336b <+122>: leaq   0x5ca251f6(%rip), %rax    ; @"is"
0x7fff32a73374 <+131>: leaq   0x5ca2520d(%rip), %rax    ; @"is not"
0x7fff32a7337d <+140>: leaq   0x5ca25224(%rip), %rax    ; @"matches"
0x7fff32a73386 <+149>: leaq   0x5ca2523b(%rip), %rax    ; @"is like"
0x7fff32a7338f <+158>: leaq   0x5ca25252(%rip), %rax    ; @"begins with"
0x7fff32a73398 <+167>: leaq   0x5ca25269(%rip), %rax    ; @"ends with"
0x7fff32a733a1 <+176>: leaq   0x5ca01300(%rip), %rax    ; @"in"
0x7fff32a733aa <+185>: leaq   0x5ca25277(%rip), %rax    ; @"contains"
0x7fff32a733d4 <+227>: leaq   0x5ca2528d(%rip), %rdx    ; @"(unknown predicate operator %ld)"