From e0fd11746f739d4cbf556f76638370b844cd458a Mon Sep 17 00:00:00 2001 From: Yurical Date: Tue, 4 Jun 2024 10:38:52 +0900 Subject: [PATCH] [Localization] Use proper postpositional particle for Korean (#1759) In Korean, postpositional particles vary depending on whether the preceding syllable ends in a consonant or a vowel. Currently there is no ability differentiate between the two types of particles, so both forms are being used at the same time. To remedy this problem, I added the relevant i18next processor to properly select and print the correct form of the particle. --- package-lock.json | 9 ++++++ package.json | 1 + src/locales/ko/ability-trigger.ts | 4 +-- src/locales/ko/battle.ts | 44 ++++++++++++++-------------- src/locales/ko/command-ui-handler.ts | 2 +- src/locales/ko/menu.ts | 4 +-- src/locales/ko/modifier-type.ts | 10 +++---- src/locales/ko/weather.ts | 4 +-- src/plugins/i18n.ts | 4 ++- 9 files changed, 47 insertions(+), 35 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0084d2e6022..8c625694f02 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "crypto-js": "^4.2.0", "i18next": "^23.11.1", "i18next-browser-languagedetector": "^7.2.1", + "i18next-korean-postposition-processor": "^1.0.0", "json-stable-stringify": "^1.1.0", "phaser": "^3.70.0", "phaser3-rex-plugins": "^1.1.84" @@ -3615,6 +3616,14 @@ "cross-fetch": "4.0.0" } }, + "node_modules/i18next-korean-postposition-processor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/i18next-korean-postposition-processor/-/i18next-korean-postposition-processor-1.0.0.tgz", + "integrity": "sha512-ruNXjI9awsFK6Ie+F9gYaMW8ciLMuCkeRjH9QkSv2Wb8xI0mnm773v3M9eua8dtvAXudIUk4p6Ho7hNkEASXDg==", + "peerDependencies": { + "i18next": ">=8.4.0" + } + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", diff --git a/package.json b/package.json index 5d1621d3ba8..2e87525aeaf 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "crypto-js": "^4.2.0", "i18next": "^23.11.1", "i18next-browser-languagedetector": "^7.2.1", + "i18next-korean-postposition-processor": "^1.0.0", "json-stable-stringify": "^1.1.0", "phaser": "^3.70.0", "phaser3-rex-plugins": "^1.1.84" diff --git a/src/locales/ko/ability-trigger.ts b/src/locales/ko/ability-trigger.ts index da263358089..7fc98edce76 100644 --- a/src/locales/ko/ability-trigger.ts +++ b/src/locales/ko/ability-trigger.ts @@ -1,6 +1,6 @@ import { SimpleTranslationEntries } from "#app/plugins/i18n"; export const abilityTriggers: SimpleTranslationEntries = { - "blockRecoilDamage" : "{{pokemonName}}(는)은 {{abilityName}} 때문에\n반동 데미지를 받지 않는다!", - "badDreams": "{{pokemonName}}(는)은\n나이트메어 때문에 시달리고 있다!", + "blockRecoilDamage" : "{{pokemonName}}[[는]] {{abilityName}} 때문에\n반동 데미지를 받지 않는다!", + "badDreams": "{{pokemonName}}[[는]]\n나이트메어 때문에 시달리고 있다!", } as const; diff --git a/src/locales/ko/battle.ts b/src/locales/ko/battle.ts index dd98a1df5fa..c6288d3d9f2 100644 --- a/src/locales/ko/battle.ts +++ b/src/locales/ko/battle.ts @@ -1,21 +1,21 @@ import { SimpleTranslationEntries } from "#app/plugins/i18n"; export const battle: SimpleTranslationEntries = { - "bossAppeared": "{{bossName}}(이)가 나타났다.", - "trainerAppeared": "{{trainerName}}(이)가\n승부를 걸어왔다!", - "trainerAppearedDouble": "{{trainerName}}(이)가\n승부를 걸어왔다!", - "trainerSendOut": "{{trainerName}}(는)은\n{{pokemonName}}(를)을 내보냈다!", - "singleWildAppeared": "앗! 야생 {{pokemonName}}(이)가\n튀어나왔다!", - "multiWildAppeared": "야생 {{pokemonName1}}(과)와\n{{pokemonName2}}(이)가 튀어나왔다!", + "bossAppeared": "{{bossName}}[[가]] 나타났다.", + "trainerAppeared": "{{trainerName}}[[가]]\n승부를 걸어왔다!", + "trainerAppearedDouble": "{{trainerName}}[[가]]\n승부를 걸어왔다!", + "trainerSendOut": "{{trainerName}}[[는]]\n{{pokemonName}}[[를]] 내보냈다!", + "singleWildAppeared": "앗! 야생 {{pokemonName}}[[가]]\n튀어나왔다!", + "multiWildAppeared": "야생 {{pokemonName1}}[[와]]\n{{pokemonName2}}[[가]] 튀어나왔다!", "playerComeBack": "돌아와, {{pokemonName}}!", - "trainerComeBack": "{{trainerName}}(는)은 {{pokemonName}}를(을) 넣어버렸다!", + "trainerComeBack": "{{trainerName}}[[는]] {{pokemonName}}[[를]] 넣어버렸다!", "playerGo": "가랏! {{pokemonName}}!", - "trainerGo": "{{trainerName}}(는)은 {{pokemonName}}를(을) 내보냈다!", - "switchQuestion": "{{pokemonName}}를(을)\n교체하시겠습니까?", - "trainerDefeated": "{{trainerName}}과(와)의\n승부에서 이겼다!", + "trainerGo": "{{trainerName}}[[는]] {{pokemonName}}[[를]] 내보냈다!", + "switchQuestion": "{{pokemonName}}[[를]]\n교체하시겠습니까?", + "trainerDefeated": "{{trainerName}}[[와]]의\n승부에서 이겼다!", "moneyWon": "상금으로\n₽{{moneyAmount}}을 손에 넣었다!", - "pokemonCaught": "신난다-!\n{{pokemonName}}(를)을 잡았다!", - "partyFull": "지닌 포켓몬이 가득 찼습니다. {{pokemonName}}를(을)\n대신해 포켓몬을 놓아주시겠습니까?", + "pokemonCaught": "신난다-!\n{{pokemonName}}[[를]] 잡았다!", + "partyFull": "지닌 포켓몬이 가득 찼습니다. {{pokemonName}}[[를]]\n대신해 포켓몬을 놓아주시겠습니까?", "pokemon": "포켓몬", "sendOutPokemon": "가랏! {{pokemonName}}!", "hitResultCriticalHit": "급소에 맞았다!", @@ -25,22 +25,22 @@ export const battle: SimpleTranslationEntries = { "hitResultOneHitKO": "일격필살!", "attackFailed": "하지만 실패했다!", "attackHitsCount": "{{count}}번 맞았다!", - "expGain": "{{pokemonName}}(는)은\n{{exp}} 경험치를 얻었다!", - "levelUp": "{{pokemonName}}(는)은\n레벨 {{level}}(으)로 올랐다!", - "learnMove": "{{pokemonName}}(는)은 새로\n{{moveName}}를(을) 배웠다!", - "learnMovePrompt": "{{pokemonName}}(는)은 새로\n{{moveName}}를(을) 배우고 싶다!…", - "learnMoveLimitReached": "그러나 {{pokemonName}}(는)은 기술을 4개\n알고 있으므로 더 이상 배울 수 없다!", + "expGain": "{{pokemonName}}[[는]]\n{{exp}} 경험치를 얻었다!", + "levelUp": "{{pokemonName}}[[는]]\n레벨 {{level}}[[로]] 올랐다!", + "learnMove": "{{pokemonName}}[[는]] 새로\n{{moveName}}[[를]] 배웠다!", + "learnMovePrompt": "{{pokemonName}}[[는]] 새로\n{{moveName}}[[를]] 배우고 싶다!…", + "learnMoveLimitReached": "그러나 {{pokemonName}}[[는]] 기술을 4개\n알고 있으므로 더 이상 배울 수 없다!", "learnMoveReplaceQuestion": "{{moveName}} 대신 다른 기술을 잊게 하겠습니까?", - "learnMoveStopTeaching": "그럼… {{moveName}}를(을)\n배우는 것을 포기하겠습니까?", - "learnMoveNotLearned": "{{pokemonName}}(는)은 {{moveName}}를(을)\n결국 배우지 않았다!", + "learnMoveStopTeaching": "그럼… {{moveName}}[[를]]\n배우는 것을 포기하겠습니까?", + "learnMoveNotLearned": "{{pokemonName}}[[는]] {{moveName}}[[를]]\n결국 배우지 않았다!", "learnMoveForgetQuestion": "어느 기술을 잊게 하고싶은가?", - "learnMoveForgetSuccess": "{{pokemonName}}(는)은 {{moveName}}를(을) 깨끗이 잊었다!", + "learnMoveForgetSuccess": "{{pokemonName}}[[는]] {{moveName}}[[를]] 깨끗이 잊었다!", "countdownPoof": "@d{32}1, @d{15}2, @d{15}… @d{15}… @d{30}@s{pb_bounce_1}짠!", "learnMoveAnd": "그리고…", "levelCapUp": "레벨의 최대치가\n{{levelCap}}까지 상승했다!", - "moveNotImplemented": "{{moveName}}(는)은 아직 구현되지 않아 사용할 수 없다…", + "moveNotImplemented": "{{moveName}}[[는]] 아직 구현되지 않아 사용할 수 없다…", "moveNoPP": "기술의 남은 포인트가 없다!", - "moveDisabled": "{{moveName}}를(을) 쓸 수 없다!", + "moveDisabled": "{{moveName}}[[를]] 쓸 수 없다!", "noPokeballForce": "본 적 없는 힘이\n볼을 사용하지 못하게 한다.", "noPokeballTrainer": "다른 트레이너의 포켓몬은 잡을 수 없다!", "noPokeballMulti": "안돼! 2마리 있어서\n목표를 정할 수가 없어…!", diff --git a/src/locales/ko/command-ui-handler.ts b/src/locales/ko/command-ui-handler.ts index c1d3d4b680f..b10534cfb92 100644 --- a/src/locales/ko/command-ui-handler.ts +++ b/src/locales/ko/command-ui-handler.ts @@ -5,5 +5,5 @@ export const commandUiHandler: SimpleTranslationEntries = { "ball": "볼", "pokemon": "포켓몬", "run": "도망간다", - "actionMessage": "{{pokemonName}}(는)은 무엇을 할까?", + "actionMessage": "{{pokemonName}}[[는]] 무엇을 할까?", } as const; diff --git a/src/locales/ko/menu.ts b/src/locales/ko/menu.ts index 8e9f132aa93..8d46dafc721 100644 --- a/src/locales/ko/menu.ts +++ b/src/locales/ko/menu.ts @@ -38,9 +38,9 @@ export const menu: SimpleTranslationEntries = { "girl": "여자", "evolving": "…오잉!?\n{{pokemonName}}의 모습이…!", "stoppedEvolving": "얼라리…?\n{{pokemonName}}의 변화가 멈췄다!", - "pauseEvolutionsQuestion": "{{pokemonName}}를(을) 진화하지 않게 만드시겠습니까?\n포켓몬 화면에서 다시 활성화시킬 수 있습니다.", + "pauseEvolutionsQuestion": "{{pokemonName}}[[를]] 진화하지 않게 만드시겠습니까?\n포켓몬 화면에서 다시 활성화시킬 수 있습니다.", "evolutionsPaused": "{{pokemonName}}의 진화가 비활성화되었다.", - "evolutionDone": "축하합니다! {{pokemonName}}(는)은\n{{evolvedPokemonName}}(으)로 진화했습니다!", + "evolutionDone": "축하합니다! {{pokemonName}}[[는]]\n{{evolvedPokemonName}}[[로]] 진화했습니다!", "dailyRankings": "일간 랭킹", "weeklyRankings": "주간 랭킹", "noRankings": "랭킹 정보 없음", diff --git a/src/locales/ko/modifier-type.ts b/src/locales/ko/modifier-type.ts index 3694a2ef523..f3b9ece6e81 100644 --- a/src/locales/ko/modifier-type.ts +++ b/src/locales/ko/modifier-type.ts @@ -12,8 +12,8 @@ export const modifierType: ModifierTypeTranslationEntries = { }, "PokemonHeldItemModifierType": { extra: { - "inoperable": "{{pokemonName}}(는)은\n이 아이템을 얻을 수 없다!", - "tooMany": "{{pokemonName}}(는)은\n이 아이템을 너무 많이 갖고 있다!", + "inoperable": "{{pokemonName}}[[는]]\n이 아이템을 얻을 수 없다!", + "tooMany": "{{pokemonName}}[[는]]\n이 아이템을 너무 많이 갖고 있다!", } }, "PokemonHpRestoreModifierType": { @@ -46,13 +46,13 @@ export const modifierType: ModifierTypeTranslationEntries = { }, "PokemonNatureChangeModifierType": { name: "{{natureName}}민트", - description: "포켓몬의 성격을 {{natureName}}(으)로 바꾸고 스타팅에도 등록한다.", + description: "포켓몬의 성격을 {{natureName}}[[로]] 바꾸고 스타팅에도 등록한다.", }, "DoubleBattleChanceBoosterModifierType": { description: "{{battleCount}}번의 배틀 동안 더블 배틀이 등장할 확률 두 배", }, "TempBattleStatBoosterModifierType": { - description: "자신의 모든 포켓몬이 5번의 배틀 동안 {{tempBattleStatName}}(이)가 한 단계 증가" + description: "자신의 모든 포켓몬이 5번의 배틀 동안 {{tempBattleStatName}}[[가]] 한 단계 증가" }, "AttackTypeBoosterModifierType": { description: "지니게 하면 {{moveType}}타입 기술의 위력이 20% 상승", @@ -97,7 +97,7 @@ export const modifierType: ModifierTypeTranslationEntries = { }, "TmModifierType": { name: "No.{{moveId}} {{moveName}}", - description: "포켓몬에게 {{moveName}}를(을) 가르침", + description: "포켓몬에게 {{moveName}}[[를]] 가르침", }, "EvolutionItemModifierType": { description: "어느 특정 포켓몬을 진화", diff --git a/src/locales/ko/weather.ts b/src/locales/ko/weather.ts index 6bfc6552b50..70654d247b6 100644 --- a/src/locales/ko/weather.ts +++ b/src/locales/ko/weather.ts @@ -15,12 +15,12 @@ export const weather: SimpleTranslationEntries = { "sandstormStartMessage": "모래바람이 불기 시작했다!", "sandstormLapseMessage": "모래바람이 세차게 분다", "sandstormClearMessage": "모래바람이 가라앉았다!", - "sandstormDamageMessage": "모래바람이\n{{pokemonPrefix}}{{pokemonName}}를(을) 덮쳤다!", + "sandstormDamageMessage": "모래바람이\n{{pokemonPrefix}}{{pokemonName}}[[를]] 덮쳤다!", "hailStartMessage": "싸라기눈이 내리기 시작했다!", "hailLapseMessage": "싸라기눈이 계속 내리고 있다", "hailClearMessage": "싸라기눈이 그쳤다!", - "hailDamageMessage": "싸라기눈이\n{{pokemonPrefix}}{{pokemonName}}를(을) 덮쳤다!", + "hailDamageMessage": "싸라기눈이\n{{pokemonPrefix}}{{pokemonName}}[[를]] 덮쳤다!", "snowStartMessage": "눈이 내리기 시작했다!", "snowLapseMessage": "눈이 계속 내리고 있다", diff --git a/src/plugins/i18n.ts b/src/plugins/i18n.ts index 9bd03b501f4..3f6469904d4 100644 --- a/src/plugins/i18n.ts +++ b/src/plugins/i18n.ts @@ -1,5 +1,6 @@ import i18next from "i18next"; import LanguageDetector from "i18next-browser-languagedetector"; +import processor, { KoreanPostpositionProcessor } from "i18next-korean-postposition-processor"; import { deConfig } from "#app/locales/de/config.js"; import { enConfig } from "#app/locales/en/config.js"; @@ -148,7 +149,7 @@ export function initI18n(): void { * A: In src/system/settings.ts, add a new case to the Setting.Language switch statement. */ - i18next.use(LanguageDetector).init({ + i18next.use(LanguageDetector).use(processor).use(new KoreanPostpositionProcessor()).init({ lng: lang, nonExplicitSupportedLngs: true, fallbackLng: "en", @@ -186,6 +187,7 @@ export function initI18n(): void { ...koConfig }, }, + postProcess: ["korean-postposition"], }); }