우리가 FText를 통해 Localization을 진행할 때
한글에서는 적절한 조사를 붙이기 위해 다음과 같이 처리한 것이 있을 것이다.
{0}|hpp(은,는)
여기서 정의는
{0}|hpp(받침이 들어올 때 쓰일 조사, 받침이 들어오지 않을 때 쓰일 조사)
그런데 언리얼엔진은 {0}에 어떤 텍스트가 들어올지 어떻게 알고 은, 는을 따로 나누어서 붙여주는 것일까?
이는 실제로 포매팅이 진행될 FTextFormatData::Format_NoLock 까지 들어가야 한다.
(여기서 Lock이 있는 이유는, 포매팅에 대해 Multithread도 지원할 수 있기 때문이다.)
FString FTextFormatData::Format_NoLock(const FPrivateTextFormatArguments& InFormatArgs)
{
// Impl
ArgumentModifierToken->TextFormatArgumentModifier->Evaluate(*PossibleArgumentValue, InFormatArgs, ResultString);
}
여기서 TextFormatArgumentModifier는 해당 텍스트가 한글이라면
FTextFormatArgumentModifier_HangulPostPositions가 들어온다.
void FTextFormatArgumentModifier_HangulPostPositions::Evaluate(const FFormatArgumentValue& InValue, const FPrivateTextFormatArguments& InFormatArgs, FString& OutResult) const
이 부분의 Implementation을 확인하면 된다.
코드를 하나하나 봐보자
if ((LastArgChar >= 0xAC00 && LastArgChar <= 0xD7A3) || (LastArgChar >= TEXT('0') && LastArgChar <= TEXT('9')))
LastArgChar가 [0xAC00, 0xD7A3] 안에 있거나 [0, 9] 에 있는지 확인한다.
여기서 0xAC00 의 유니코드는 '가'
0xD7A3의 유니코드는 '힣' 이다.
즉
마지막 문자가 한글이거나 숫자인지를 확인한다.
bool bIsConsonant = ((LastArgChar - 0xAC00) % 28 != 0) || LastArgChar == TEXT('0') || LastArgChar == TEXT('1') || LastArgChar == TEXT('3') || LastArgChar == TEXT('6') || LastArgChar == TEXT('7') || LastArgChar == TEXT('8');
숫자부터
LastArgChar가 0, 1, 3, 6, 7, 8 이라면 받침이 있는 것이다.
예를들어
'1은' = '일은' 이고
'2는' = "이는" 이므로 0, 1, 3, 6, 7, 8 은 받침이 있는 조사로 처리되어야 할 것이다.
또한
(LastArgChar - 0xAC00) % 28 != 0 이라는 코드가 있다.
이는 유니코드의 받침이 어떻게 처리되는지 알아야 한다.
우리가 예를들어
길 이라는 텍스트를 쓴다고 해보자 이 길이라는 텍스트는
AE38 이다.
한글 음정의 유니코드는 다음과 같이 정해진다.
초성 * 21 * 28 + 중성 * 28 + 종성
여기서 초성 | 중성 | 종성은 우리의 자음 모음 순서이다.
길은 ㄱ + ㅣ + ㄹ 이며
인덱스는 0, 20, 8 이다.
한글은 19개의 자음과 10개의 모음인데
왜 ㅣ 은 인덱스가 20 이고
ㄹ은 인덱스가 8일까?
이건 초성, 중성, 종성이 어떻게 이루어져 있는지를 봐야 하는데, 표를 보면 이해가 쉬울 것이라고 생각한다
초성 인덱스
0 | ㄱ | 기본 자음 |
1 | ㄲ | 된소리(쌍기역) |
2 | ㄴ | |
3 | ㄷ | |
4 | ㄸ | 된소리(쌍디귿) |
5 | ㄹ | |
6 | ㅁ | |
7 | ㅂ | |
8 | ㅃ | 된소리(쌍비읍) |
9 | ㅅ | |
10 | ㅆ | 된소리(쌍시옷) |
11 | ㅇ | |
12 | ㅈ | |
13 | ㅉ | 된소리(쌍지읒) |
14 | ㅊ | |
15 | ㅋ | |
16 | ㅌ | |
17 | ㅍ | |
18 | ㅎ |
중성 인덱스
0 | ㅏ | 기본 단모음 |
1 | ㅐ | |
2 | ㅑ | |
3 | ㅒ | |
4 | ㅓ | |
5 | ㅔ | |
6 | ㅕ | |
7 | ㅖ | |
8 | ㅗ | 단모음 (복합 조합 가능한 모음) |
9 | ㅘ | ㅗ + ㅏ |
10 | ㅙ | ㅗ + ㅐ |
11 | ㅚ | ㅗ + ㅣ |
12 | ㅛ | |
13 | ㅜ | |
14 | ㅝ | ㅜ + ㅓ |
15 | ㅞ | ㅜ + ㅔ |
16 | ㅟ | ㅜ + ㅣ |
17 | ㅠ | |
18 | ㅡ | |
19 | ㅢ | ㅡ + ㅣ |
20 | ㅣ |
종성 인덱스
0 | (없음) | 받침 없음 |
1 | ㄱ | 기본 자음 |
2 | ㄲ | 쌍기역 |
3 | ㄳ | ㄱ + ㅅ (겹받침) |
4 | ㄴ | |
5 | ㄵ | ㄴ + ㅈ |
6 | ㄶ | ㄴ + ㅎ |
7 | ㄷ | |
8 | ㄹ | |
9 | ㄺ | ㄹ + ㄱ |
10 | ㄻ | ㄹ + ㅁ |
11 | ㄼ | ㄹ + ㅂ |
12 | ㄽ | ㄹ + ㅅ |
13 | ㄾ | ㄹ + ㅌ |
14 | ㄿ | ㄹ + ㅍ |
15 | ㅀ | ㄹ + ㅎ |
16 | ㅁ | |
17 | ㅂ | |
18 | ㅄ | ㅂ + ㅅ |
19 | ㅅ | |
20 | ㅆ | 쌍시옷 |
21 | ㅇ | |
22 | ㅈ | |
23 | ㅊ | |
24 | ㅋ | |
25 | ㅌ | |
26 | ㅍ | |
27 | ㅎ |
종성은 없을수도 있고,
초성과 다른 복합자음(겹받침)이 받침으로 들어갈 수 있기 때문이다.
위 표에 맞춰서 '길' 이라는 텍스트를 다시 계산하면
길은 ㄱ + ㅣ + ㄹ 이며
인덱스는 0, 20, 8 이다.
이 인덱스를 위의 식에 계산해본다면 길은 568 이라는 값이 나오고
한글 유니코드의 시작인 0xAC00 에 568를 더하면 0xAE38 이 나온다.
그럼 다시 위의 FText 코드로 돌아가본다면
(LastArgChar - 0xAC00) % 28 != 0
이는 결국
LastArgChar가 종성 28개로 Modular 연산을 했을때 0이 아니라면 받침이 있다는 뜻이다. 라는것으로 확인할 수 있다.
한글의 뒷 글자가 받침이 있다면 이것은 '받침이 있을 때 들어오는 조사' 로 포매팅 되어야 한다는 것이다.
const bool bIsRieul = ((LastArgChar - 0xAC00) % 28 == 8) || LastArgChar == TEXT('1') || LastArgChar == TEXT('7') || LastArgChar == TEXT('8');
if (bIsRieul)
{
bIsConsonant = false;
}
bIsRieul은 해당 문자가 ㄹ 받침을 쓰는지 확인한다
'ㄹ' 은 인덱스로 8 이므로 LastArgChar - 0xAC00 % 28 (종성갯수) 했을 때 8이 나오면 이는 ㄹ이다.
ㄹ받침인 경우에는 '받침이 없는 조사'가 들어와야 한다는 것이다.
if (bIsConsonant)
{
OutResult.AppendChars(ConsonantSuffix.StringPtr, ConsonantSuffix.StringLen);
}
else
{
OutResult.AppendChars(VowelSuffix.StringPtr, VowelSuffix.StringLen);
}
받침이 있다는 것은 곧, 자음이 이곳에 들어왔다는 것이고 그래서 bIsConsonant를 사용한다.
이게 true 라면 '은' 이 붙을 것이고
false라면 '는' 이 붙을 것이다.
'UnrealEngine > Impl' 카테고리의 다른 글
Unreal Engine의 FText 내부까지 알아보기 (0) | 2024.07.21 |
---|---|
NewObject와 SpawnActor (0) | 2023.10.03 |
SpawnActor와 SpawnActorDeferred (0) | 2023.10.02 |
UnrealEngine의 UObject 와 Components (2) | 2023.09.30 |
Hard References와 Soft References (0) | 2023.09.28 |