Mac App 에서의 i18n과 Mirroring 지원

panicstyle·2020년 12월 2일
0
post-thumbnail

이 글은 Mac용 App 에서 아랍어 지원 프로젝트 진행 중 발생한 문제에 대한 해결책을 정리한 것입니다.

해야 할 일은 다음과 같습니다.

  • Mac용으로 개발된 앱이 있습니다.
  • 한글, 영문뿐만 아니라 아랍어도 지원해야 합니다.
  • Mac OS 의 언어 설정이 아닌, 앱 내부의 언어 설정에 따라 앱의 사용언어가 변경되어야 합니다.

앱의 언어 설정에 따라 언어를 변경하는 것은 그렇게 어렵지 않습니다. 언어별로 리소스 파일을 생성하여 TextField 혹은 Label 등에 해당 Key에 맞는 문자열을 대입하면 됩니다.

in Locale-ko.strings
"Confirm" = "확인";

in Locale-en.strings
"Confirm" = "OK";

self.okButton.title = [[NSBundle mainBundle] localizedStringForKey:@"Confirm" value:nil table:@"Locale-en"];

아랍어도 언어는 동일한 방법으로 적용하면 됩니다. 그런데 문제는 아랍어의 경우는 오른쪽에서 왼쪽으로 표기하기 때문에 기존 좌측정렬을 우측정렬로 변경해야 하며, 화면의 컨트롤들도 모두 좌우가 변경되어야 합니다.
그냥 2개의 화면을 준비해서 언어별로 필요한 화면을 표시하면 되겠지만, 기존에 개발된 앱에 적용하기에는 시간이 많이 걸리는 작업입니다.

iOS의 UIView 에서는 semanticContentAttribute 라는 방법으로 간단히 적용할 수 있습니다. 아래의 링크를 참고하세요.
https://medium.com/if-let-swift-programming/working-with-localization-in-swift-4a87f0d393a4
안타깝게도 NSView 에는 semanticContentAttribute 가 지원되지 않습니다. NSView 에는 userInterfaceLayoutDirection 를 사용해야 합니다.

한가지 문제는 UIView의 semanticContentAttribut는 변경즉시 UI 가 변경되지만, NSView의 userInterfaceLayoutDirection 는 즉시 변경되지 않습니다.
언어 변경시 앱을 재시작한다면 모르지만, 재시작하지 않고 즉시 반영해야 한다면 아래와 코드르 참고하세요.

- (IBAction)btnClicked:(id)sender {

    AppDelegate *appDelegate = [NSApplication sharedApplication].delegate;
    NSUserInterfaceLayoutDirection direction;
    if (appDelegate.isLayoutMode) {
        direction = NSUserInterfaceLayoutDirectionLeftToRight;
    } else {
        direction = NSUserInterfaceLayoutDirectionRightToLeft;
    }
    self.view.userInterfaceLayoutDirection = direction;
    
    [self setDirection:self.view withDirection:direction];

    appDelegate.isLayoutMode = !appDelegate.isLayoutMode;
    
    NSView *parent = self.view.superview;
    [self.view removeFromSuperview];
    [parent addSubview:self.view]; //reloads the view from the nib//
}

- (void)setDirection:(NSView *)view withDirection:(NSUserInterfaceLayoutDirection)direction
{
    NSArray *subviews = [view subviews];
    for (NSView *subview in subviews) {
        subview.userInterfaceLayoutDirection = direction;
        if ([subview isKindOfClass:[NSTextField class]]) {
            NSLog(@"NSTextField");
            NSTextField *textField = (NSTextField *)subview;
            textField.alignment = !textField.alignment;
        } else if ([subview isKindOfClass:[NSImageView class]]) {
            NSLog(@"NSImageView");
            NSImageView *imageView = (NSImageView *)subview;
            NSImage *image = imageView.image;
            imageView.image = [self flipImageHorizontally:image];
        }
        [self setDirection:subview withDirection:direction];
    }
}

추가로, 위의 방법으로도 TableView 의 스크롤바의 위치는 변경되지 않습니다. TableView는 CustomScrollView 를 Extention 하여 drawRect 시 아래와 같이 스크롤바를 변경해 주어야 합니다.

- (void)drawRect:(NSRect)dirtyRect {
    [super drawRect:dirtyRect];
    
    // Drawing code here.
    AppDelegate *appDelegate = [NSApplication sharedApplication].delegate;
    NSRect scrollViewRect = [self frame];
    
    NSScroller *verticalScroller = [self verticalScroller];
    NSRect verticalScrollerFrame = [verticalScroller frame];

    NSClipView *clipView = [self contentView];
    NSRect clipViewFrame = [clipView frame];

    if (appDelegate.isLayoutMode) {
        verticalScrollerFrame.origin.x = scrollViewRect.size.width - verticalScrollerFrame.size.width - 1;
        clipViewFrame.origin.x = 1.0;
        NSLog(@"CustomScrollView isLeftToRight");
    } else {
        verticalScrollerFrame.origin.x = 1.0;
        /*
         OS 일반설정에서 스크롤 막대 보기 설정에 따라 "항상"이 아닌 경우에는 스크롤 width 가 전체 width 에 공간을 차지하지 않는다.
         설정값을 확인하는 방법은 스크롤이 hidden 되지 않았을 때 scrollView의 width와 clipView의 width 가 2보다 크면 "항상"으로 설정된 것임
         차이값 2는 border 가 차지하는 것으로 보임
         */
        NSLog(@"scrollView width=%f, clipView width=%f", scrollViewRect.size.width, clipViewFrame.size.width);
        if (!verticalScroller.isHidden && (scrollViewRect.size.width - clipViewFrame.size.width) > 2) {
            clipViewFrame.origin.x = verticalScrollerFrame.size.width + 1;
        }
        NSLog(@"CustomScrollView isRightToLeft");
    }

    [verticalScroller setFrame:verticalScrollerFrame];

    [clipView setFrame:clipViewFrame];
    [self setVerticalScroller:verticalScroller];
}

샘플코드 링크 https://github.com/panicstyle/MirroringSample

profile
이것저것 개발자

0개의 댓글