feat: update typecheck script for incremental compilation and add JosaUtil for Korean postposition handling
This commit is contained in:
@@ -10,7 +10,7 @@
|
||||
"dev": "tsdown -c ../../tsdown.config.ts -F @sammo-ts/common --watch",
|
||||
"lint": "node -e \"console.log('lint not configured')\"",
|
||||
"test": "vitest run --config vitest.config.ts",
|
||||
"typecheck": "tsc --noEmit"
|
||||
"typecheck": "tsc -b"
|
||||
},
|
||||
"dependencies": {
|
||||
"js-sha512": "^0.9.0"
|
||||
|
||||
@@ -4,6 +4,7 @@ export * from './util/BytesLike.js';
|
||||
export * from './util/convertBytesLikeToArrayBuffer.js';
|
||||
export * from './util/convertBytesLikeToUint8Array.js';
|
||||
export * from './util/LiteHashDRBG.js';
|
||||
export * from './util/JosaUtil.js';
|
||||
export * from './util/RNG.js';
|
||||
export * from './util/RandUtil.js';
|
||||
export * from './util/TestRNG.js';
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
export type JosaKey =
|
||||
| '은'
|
||||
| '이'
|
||||
| '과'
|
||||
| '이나'
|
||||
| '을'
|
||||
| '으로'
|
||||
| '이라'
|
||||
| '이랑';
|
||||
|
||||
const DEFAULT_POSTPOSITION: Record<JosaKey, string> = {
|
||||
은: '는',
|
||||
이: '가',
|
||||
과: '와',
|
||||
이나: '나',
|
||||
을: '를',
|
||||
으로: '로',
|
||||
이라: '라',
|
||||
이랑: '랑',
|
||||
};
|
||||
|
||||
const buildMapPostPosition = (): Record<string, JosaKey> => {
|
||||
const map: Record<string, JosaKey> = {};
|
||||
for (const [key, value] of Object.entries(DEFAULT_POSTPOSITION)) {
|
||||
const k = key as JosaKey;
|
||||
map[key] = k;
|
||||
map[value] = k;
|
||||
map[`(${key})${value}`] = k;
|
||||
}
|
||||
return map;
|
||||
};
|
||||
|
||||
const MAP_POSTPOSITION = buildMapPostPosition();
|
||||
|
||||
const REG_INVALID_CHAR = /[^a-zA-Z0-9ㄱ-ㅎ가-힣一-廓\s]+/gu;
|
||||
const REG_TARGET_CHAR = /^[\s\S]*?(\S*)\s*$/u;
|
||||
|
||||
const KO_START_CODE = 0xac00;
|
||||
const KO_FINISH_CODE = 0xd7a3;
|
||||
const JONGSUNG_RIEUL = 8;
|
||||
|
||||
const getLastChar = (text: string): string => {
|
||||
const cleaned = text
|
||||
.replace(REG_INVALID_CHAR, ' ')
|
||||
.replace(REG_TARGET_CHAR, '$1')
|
||||
.trim();
|
||||
if (!cleaned) {
|
||||
return '';
|
||||
}
|
||||
const chars = Array.from(cleaned);
|
||||
return chars[chars.length - 1] ?? '';
|
||||
};
|
||||
|
||||
const getDigitJongsung = (
|
||||
digit: number
|
||||
): { has: boolean; rieul: boolean } => {
|
||||
switch (digit) {
|
||||
case 0:
|
||||
case 3:
|
||||
case 6:
|
||||
return { has: true, rieul: false };
|
||||
case 1:
|
||||
case 7:
|
||||
case 8:
|
||||
return { has: true, rieul: true };
|
||||
default:
|
||||
return { has: false, rieul: false };
|
||||
}
|
||||
};
|
||||
|
||||
const hasJongsung = (text: string, isRo: boolean): boolean => {
|
||||
const lastChar = getLastChar(text);
|
||||
if (!lastChar) {
|
||||
return false;
|
||||
}
|
||||
const code = lastChar.codePointAt(0);
|
||||
if (code === undefined) {
|
||||
return false;
|
||||
}
|
||||
if (code >= KO_START_CODE && code <= KO_FINISH_CODE) {
|
||||
const jongsung = (code - KO_START_CODE) % 28;
|
||||
if (jongsung === 0) {
|
||||
return false;
|
||||
}
|
||||
if (isRo && jongsung === JONGSUNG_RIEUL) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (lastChar >= 'ㄱ' && lastChar <= 'ㅎ') {
|
||||
if (isRo && lastChar === 'ㄹ') {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (lastChar >= '0' && lastChar <= '9') {
|
||||
const { has, rieul } = getDigitJongsung(Number(lastChar));
|
||||
if (isRo && rieul) {
|
||||
return false;
|
||||
}
|
||||
return has;
|
||||
}
|
||||
const lower = lastChar.toLowerCase();
|
||||
const isVowel = ['a', 'e', 'i', 'o', 'u', 'y'].includes(lower);
|
||||
return !isVowel;
|
||||
};
|
||||
|
||||
export class JosaUtil {
|
||||
static pick(text: string | null | undefined, wJongsung: string, woJongsung = ''): string {
|
||||
const normalizedText = text ?? '';
|
||||
let withJongsung = wJongsung;
|
||||
let withoutJongsung = woJongsung;
|
||||
|
||||
if (!withoutJongsung) {
|
||||
const mapped = MAP_POSTPOSITION[wJongsung];
|
||||
if (!mapped) {
|
||||
throw new Error('올바르지 않은 조사 지정');
|
||||
}
|
||||
withJongsung = mapped;
|
||||
withoutJongsung = DEFAULT_POSTPOSITION[mapped];
|
||||
}
|
||||
|
||||
const isRo = withJongsung === '으로';
|
||||
return hasJongsung(normalizedText, isRo)
|
||||
? withJongsung
|
||||
: withoutJongsung;
|
||||
}
|
||||
|
||||
static put(text: string, wJongsung: string, woJongsung = ''): string {
|
||||
return text + JosaUtil.pick(text, wJongsung, woJongsung);
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ import type {
|
||||
} from '../../engine.js';
|
||||
import { createGeneralPatchEffect, createLogEffect } from '../../engine.js';
|
||||
import { LogCategory, LogFormat, LogScope } from '../../../logging/types.js';
|
||||
import { JosaUtil } from '@sammo-ts/common';
|
||||
|
||||
export interface AssignmentArgs {
|
||||
destGeneralId: number;
|
||||
@@ -103,6 +104,8 @@ export class ActionResolver<
|
||||
const cityName = this.env.formatCityName
|
||||
? this.env.formatCityName(destCity)
|
||||
: destCity.name;
|
||||
const cityJosa = JosaUtil.pick(cityName, '로');
|
||||
const generalJosa = JosaUtil.pick(destGeneral.name, '을');
|
||||
const yearMonth = resolveLastAssignment(context);
|
||||
|
||||
const effects: Array<GeneralActionEffect<TriggerState>> = [
|
||||
@@ -117,7 +120,7 @@ export class ActionResolver<
|
||||
|
||||
effects.push(
|
||||
createLogEffect(
|
||||
`<Y>${context.general.name}</>에 의해 <G><b>${cityName}</b></>로 발령됐습니다.`,
|
||||
`<Y>${context.general.name}</>에 의해 <G><b>${cityName}</b></>${cityJosa} 발령됐습니다.`,
|
||||
{
|
||||
scope: LogScope.GENERAL,
|
||||
category: LogCategory.ACTION,
|
||||
@@ -128,7 +131,7 @@ export class ActionResolver<
|
||||
);
|
||||
effects.push(
|
||||
createLogEffect(
|
||||
`<Y>${destGeneral.name}</>을 <G><b>${cityName}</b></>로 발령했습니다.`,
|
||||
`<Y>${destGeneral.name}</>${generalJosa} <G><b>${cityName}</b></>${cityJosa} 발령했습니다.`,
|
||||
{
|
||||
scope: LogScope.GENERAL,
|
||||
category: LogCategory.ACTION,
|
||||
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
createNationPatchEffect,
|
||||
} from '../../engine.js';
|
||||
import { LogCategory, LogFormat, LogScope } from '../../../logging/types.js';
|
||||
import { JosaUtil } from '@sammo-ts/common';
|
||||
|
||||
export interface AwardArgs {
|
||||
isGold: boolean;
|
||||
@@ -148,9 +149,10 @@ export class ActionResolver<
|
||||
} as Partial<Nation>, nation.id),
|
||||
];
|
||||
|
||||
const amountJosa = JosaUtil.pick(amountText, '을');
|
||||
effects.push(
|
||||
createLogEffect(
|
||||
`${label} ${amountText} 포상으로 받았습니다.`,
|
||||
`${label} ${amountText}${amountJosa} 포상으로 받았습니다.`,
|
||||
{
|
||||
scope: LogScope.GENERAL,
|
||||
category: LogCategory.ACTION,
|
||||
@@ -161,7 +163,7 @@ export class ActionResolver<
|
||||
);
|
||||
effects.push(
|
||||
createLogEffect(
|
||||
`<Y>${context.destGeneral.name}</>에게 ${label} ${amountText} 수여했습니다.`,
|
||||
`<Y>${context.destGeneral.name}</>에게 ${label} ${amountText}${amountJosa} 수여했습니다.`,
|
||||
{
|
||||
scope: LogScope.GENERAL,
|
||||
category: LogCategory.ACTION,
|
||||
|
||||
Reference in New Issue
Block a user