한자를 히라가나로 바꿔보자!

SteadySlower·2022년 12월 12일
0

한자를 히라가나로 바꾸기

기존의 단어장에 한자와 한자를 풀어서 읽은 히라가나를 둘 다 저장하기 위해서는 일본어를 2번 입력해야 했습니다. 같은 단어를 한자로 한번, 히라가나로 한번 똑같이 입력해야 했습니다. 참 귀찮은 일입니다. 그리고 로마자로 일본어를 입력할 때 자동으로 한자로 바꾸어 주기 때문에 F6를 통해서 매번 히라가나로 바꾸어 주어야 합니다. F6는 한국 사람이 자주 사용하는 키가 아니죠. 매우 귀찮은 일입니다.

제가 생각하는 개발자의 최대 장점 중에 하나가 귀찮은 일을 컴퓨터에게 시킬 수 있다는 것인데요. 이번 포스팅에서 이 귀찮은 일을 컴퓨터에게 시켜봅시다.

살짝 복잡한 개념이 나옵니다. 집중해서 포스팅을 작성해보겠습니다.

배경 지식

CFStringTokenizer

Apple Developer Documentation

CFStringTokenizer는 하나의 string을 언어 맞추어 단어, 문장 단위로 끊어주는 객체입니다. 특히 일본어나 중국어 처럼 단어와 단어 사이에 띄어쓰기로 구분하지 않는 언어들을 지원합니다. 이 객체를 통해서 각 토큰의 Latin transcription (알파벳으로 소리나는대로 받아쓴 것)을 얻을 수 있습니다.

우리는 한자를 히라가나로 바꾸기 위해 이 객체를 사용합니다. 이 객체는 우리가 입력한 한자가 포함된 일본어를 단어 단위로 끊어주고 그 단어를 Latin transcription으로 바꾼 다음에 다시 히라가나로 바꾸어줍니다.

CFString

Apple Developer Documentation

CFString은 CFStringTokenizer 객체가 다루는 문자열의 타입입니다. Swift에서 일반적으로 다루는 String 타입으로는 CFStringTokenizer를 활용할 수 없고 이 타입을 활용해야 합니다.

위 문서를 참고하면 NSString과 비용 없이 타입 변환을 할 수 있다고 합니다. 아래 코드에서도 CFMutableString의 자리에 NSMutableString을 사용하는 것을 볼 수 있습니다. (아무래도 NSString이 조금이라도 익숙하네요.)

코드

이제 부터는 코드의 주석으로 설명을 하겠습니다. 코드로 보는 것이 더 이해가 빠릅니다.

CFStringTokenizer의 extension

아래 코드에는 CFStringTokenzier의 객체 안에 extension을 통해 필요한 기능을 구현합니다.

private extension CFStringTokenizer {
    
    // CFStringTokenizer 안에 있는 CFString을 가지고 string으로 바꾸어주는 computed property
        // kCFStringTransformLatinHiragana과 kCFStringTransformLatinKatakana는 CFString 타입이지만 실제로 데이터를 담고 있는 것은 아니고
        // 라틴을 히라가나로 바꾸는 방식을 나타내는 identifier로의 역할을 하는 글로벌 변수임 (https://developer.apple.com/documentation/corefoundation/kcfstringtransformlatinhiragana)
    var hiragana: String { string(to: kCFStringTransformLatinHiragana) }
    var katakana: String { string(to: kCFStringTransformLatinKatakana) }
    
    // CFStringTokenizer안에 있는 토큰을 하나하나 풀어서 gana로 변경한 다음에 하나의 string으로 합쳐서 리턴함.
    private func string(to transform: CFString) -> String {
        var output: String = ""
        // 다음 토큰으로 이동해서 gana letter로 바꾸고 output에 더해준다.
        while !CFStringTokenizerAdvanceToNextToken(self).isEmpty {
            output.append(letter(to: transform))
        }
        return output
    }

    // 토큰 하나를 gana string으로 하나로 바꾸어 주는 함수
    private func letter(to transform: CFString) -> String {
        
        // 현재 Token을 복사해오는데 Lantin Transction으로 가져온다. (여기서 한자가 Latin Transcription으로 바뀜)
            // 그리고 나서 NSString -> NSMutableString으로 바꾼다 (Latin을 gana로 바꾸기 위해서 mutable로)
        let mutableString: NSMutableString =
            CFStringTokenizerCopyCurrentTokenAttribute(self, kCFStringTokenizerAttributeLatinTranscription)
                .flatMap { $0 as? NSString }
                .map { $0.mutableCopy() }
                .flatMap { $0 as? NSMutableString } ?? NSMutableString()
        
        // CFMutableString을 identifier에 맞게 변환해준다. (여기서는 latin을 gana로)
						// CFString 대신에 NSString을 사용해도 된다.
            // mutableString 변수에 할당해줌
        CFStringTransform(mutableString, nil, transform, false)
        
        // NSMutableString을 다시 String으로 바꾸어서 리턴한다.
        return mutableString as String
    }
}

한자가 포함된 String을 가나로 바꾸는 함수

아래 코드에는 위에서 설명한 CFStringTokenizer의 인스턴스를 만드는 부분 있습니다. CFStringTokenizer의 인스턴스를 만들기 위해서 필요한 인자들에 대한 설명은 아래와 같습니다.

private enum Kana { case hiragana, katakana }

private func convert(_ input: String, to kana: Kana = .hiragana) -> String {
    // 주어진 String의 공백과 \n을 모두 없앤다.
    let trimmed: String = input.trimmingCharacters(in: .whitespacesAndNewlines)
    
    // CFStringTokenizer 객체를 만든다.
    let tokenizer: CFStringTokenizer =
        CFStringTokenizerCreate(kCFAllocatorDefault, // 메모리 할당하는 객체
                                trimmed as CFString, // token으로 쪼갤 CFString
                                CFRangeMake(0, trimmed.utf16.count), // token으로 쪼갤 CFString의 range (= 전체)
                                kCFStringTokenizerUnitWordBoundary, // 단어 단위로 쪼갠다
                                Locale(identifier: "ja") as CFLocale) // 언어 설정 (일본어)
    
    // extension으로 구현한 computed property를 통해서 히라가나나 카타카나를 리턴한다.
    switch kana {
    case .hiragana: return tokenizer.hiragana
    case .katakana: return tokenizer.katakana
    }
}

extension String {
    var hiragana: String { convert(self, to: .hiragana) }
    var katakana: String { convert(self, to: .katakana) }
}

마치며

위 코드는 아래 깃허브에서 가져왔습니다. 저에게는 복잡한 개념이었는데 코드를 가독성 있게 짜시는 분이라 쉽게 이해했네요. 도쿄에 사시는 개발자분 같던데 감사합니다ㅎㅎ

Kanji to hiragana or katakana

profile
백과사전 보다 항해일지(혹은 표류일지)를 지향합니다.

0개의 댓글