From f5f597d73d057a40ac7866e9717546fb733d2c8b Mon Sep 17 00:00:00 2001 From: mineseo-kim Date: Thu, 9 Apr 2026 09:23:14 +0900 Subject: [PATCH] fix(i18n): restore ko locale and remove UTF-8 BOM - Replace mojibake in ko.ts with proper Korean aligned to en.ts. - Strip UTF-8 BOM from the 9 other text files (docs, commands, services, tests, en.ts). Made-with: Cursor --- Docs/Plans/Fishing_MiniGame_Plan.md | 2 +- ..._Fishing_MiniGame_Phase1_Implementation.md | 2 +- Docs/index.md | 2 +- src/commands/fishing.ts | 2 +- src/commands/music.ts | 2 +- src/i18n/locales/en.ts | 2 +- src/i18n/locales/ko.ts | 484 +++++++++--------- src/services/FishingService.ts | 2 +- src/services/MusicService.ts | 2 +- tests/services/FishingService.test.ts | 2 +- 10 files changed, 249 insertions(+), 253 deletions(-) diff --git a/Docs/Plans/Fishing_MiniGame_Plan.md b/Docs/Plans/Fishing_MiniGame_Plan.md index b05a963..fc68c5d 100644 --- a/Docs/Plans/Fishing_MiniGame_Plan.md +++ b/Docs/Plans/Fishing_MiniGame_Plan.md @@ -1,4 +1,4 @@ -# 낚시 미니게임 구현 기획안 +# 낚시 미니게임 구현 기획안 이 문서는 Kord의 `낚시(Fishing)` 미니게임 시스템에 대한 설계 방향과 구현 범위를 정리한 기획서입니다. diff --git a/Docs/WorkDone/2026-03-31_Fishing_MiniGame_Phase1_Implementation.md b/Docs/WorkDone/2026-03-31_Fishing_MiniGame_Phase1_Implementation.md index cb2e422..bed2600 100644 --- a/Docs/WorkDone/2026-03-31_Fishing_MiniGame_Phase1_Implementation.md +++ b/Docs/WorkDone/2026-03-31_Fishing_MiniGame_Phase1_Implementation.md @@ -1,4 +1,4 @@ -# 2026-03-31 낚시 미니게임 Phase 1 구현 +# 2026-03-31 낚시 미니게임 Phase 1 구현 ## 개요 diff --git a/Docs/index.md b/Docs/index.md index ffe098a..a587aa8 100644 --- a/Docs/index.md +++ b/Docs/index.md @@ -1,4 +1,4 @@ -# Kord Documentation Index +# Kord Documentation Index 이 루트 색인 문서는 프로젝트 내의 모든 구조화된 문서를 카테고리별로 모아 탐색을 돕기 위해 작성되었습니다. diff --git a/src/commands/fishing.ts b/src/commands/fishing.ts index 291f9fc..b4fc299 100644 --- a/src/commands/fishing.ts +++ b/src/commands/fishing.ts @@ -1,4 +1,4 @@ -import { +import { ChannelType, ChatInputCommandInteraction, SlashCommandBuilder, diff --git a/src/commands/music.ts b/src/commands/music.ts index 81b6928..afb7514 100644 --- a/src/commands/music.ts +++ b/src/commands/music.ts @@ -1,4 +1,4 @@ -import { ChatInputCommandInteraction, GuildMember, SlashCommandBuilder } from 'discord.js'; +import { ChatInputCommandInteraction, GuildMember, SlashCommandBuilder } from 'discord.js'; import { SupportedLocale, t } from '../i18n'; import { MusicService } from '../services/MusicService'; import { logger } from '../utils/logger'; diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts index b5c7c63..1533d86 100644 --- a/src/i18n/locales/en.ts +++ b/src/i18n/locales/en.ts @@ -1,4 +1,4 @@ -import { TranslationSchema } from '../types'; +import { TranslationSchema } from '../types'; /** * English translations ??the DEFAULT and FALLBACK locale. diff --git a/src/i18n/locales/ko.ts b/src/i18n/locales/ko.ts index 855a598..b55d263 100644 --- a/src/i18n/locales/ko.ts +++ b/src/i18n/locales/ko.ts @@ -1,261 +1,259 @@ -import { TranslationSchema } from '../types'; +import { TranslationSchema } from '../types'; /** - * ?쒓뎅??踰덉뿭 ?뚯씪. - * 紐⑤뱺 ?ㅺ? en.ts?€ 1:1 ?€?묐릺?댁빞 ?⑸땲?? + * 한국어 번역. en.ts와 키 구조가 1:1로 대응해야 합니다. */ export const ko: TranslationSchema = { - // ?€?€ ?먮윭 硫붿떆吏€ ?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€ errors: { E1001: { userMessage: '사용자 제한 값이 올바르지 않습니다.', - resolution: '0?먯꽌 99 ?ъ씠???レ옄瑜??낅젰?댁<?몄슂. (0 = 臾댁젣??', + resolution: '0에서 99 사이의 숫자를 입력해 주세요. (0 = 무제한)', }, E1002: { - userMessage: '梨꾨꼸 ?대쫫 ?뺤떇???щ컮瑜댁? ?딆뒿?덈떎.', - resolution: '?좏슚??梨꾨꼸 ?대쫫???낅젰?댁<?몄슂. (理쒕? 100??', + userMessage: '채널 이름 형식이 올바르지 않습니다.', + resolution: '올바른 채널 이름을 입력해 주세요. (최대 100자)', }, E1003: { - userMessage: '?먭린 ?먯떊?먭쾶?????묒뾽???섑뻾?????놁뒿?덈떎.', + userMessage: '자기 자신에게는 이 작업을 수행할 수 없습니다.', }, E1004: { - userMessage: '?좏깮???좎?媛€ ?뚯꽦 梨꾨꼸???놁뒿?덈떎.', - resolution: '?묒뾽???섑뻾?섍린 ?꾩뿉 ?대떦 ?좎?媛€ 梨꾨꼸???덈뒗吏€ ?뺤씤?댁<?몄슂.', + userMessage: '선택한 사용자가 음성 채널에 없습니다.', + resolution: '작업 전에 해당 사용자가 채널에 있는지 확인해 주세요.', }, E2001: { - userMessage: '遊뉗뿉 梨꾨꼸??愿€由ы븷 沅뚰븳??遺€議깊빀?덈떎.', - resolution: '?쒕쾭 愿€由ъ옄?먭쾶 遊뉗쓽 "梨꾨꼸 愿€由? 沅뚰븳???뺤씤?대떖?쇨퀬 ?붿껌?섏꽭??', + userMessage: '봇에게 채널을 관리할 권한이 부족합니다.', + resolution: '서버 관리자에게 봇에게 「채널 관리」 권한을 부여해 달라고 요청해 주세요.', }, E2002: { - userMessage: '遊뉗뿉 ?뚯꽦 梨꾨꼸 愿€??沅뚰븳??遺€議깊빀?덈떎.', - resolution: '?쒕쾭 愿€由ъ옄?먭쾶 遊뉗쓽 "梨꾨꼸 愿€由?, "??븷 愿€由?, "硫ㅻ쾭 ?대룞" 沅뚰븳???뺤씤?대떖?쇨퀬 ?붿껌?섏꽭??', + userMessage: '봇에게 음성 채널 관련 권한이 부족합니다.', + resolution: + '서버 관리자에게 봇에게 「채널 관리」, 「역할 관리」, 「멤버 이동」 권한을 부여해 달라고 요청해 주세요.', }, E2003: { - userMessage: '??紐낅졊?대? ?ъ슜??沅뚰븳???놁뒿?덈떎.', - resolution: '??紐낅졊?대뒗 愿€由ъ옄 沅뚰븳???꾩슂?⑸땲??', + userMessage: '이 명령을 사용할 권한이 없습니다.', + resolution: '이 명령은 관리자 권한이 필요합니다.', }, E2004: { - userMessage: '梨꾨꼸 ?뚯쑀?먮쭔 ??湲곕뒫???ъ슜?????덉뒿?덈떎.', + userMessage: '채널 소유자만 이 컨트롤을 사용할 수 있습니다.', }, E2005: { - userMessage: '?쒖꽦???꾩떆 ?뚯꽦 梨꾨꼸??李몄뿬 以묒씠?댁빞 ?ъ슜?????덉뒿?덈떎.', - resolution: '?꾩떆 ?뚯꽦 梨꾨꼸??李몄뿬?????ㅼ떆 ?쒕룄?댁<?몄슂.', + userMessage: '활성화된 임시 음성 채널에 있어야 이 기능을 사용할 수 있습니다.', + resolution: '임시 음성 채널에 참가한 뒤 다시 시도해 주세요.', }, E3001: { - userMessage: '?붿껌??泥섎━?섎뒗 以??대? ?ㅻ쪟媛€ 諛쒖깮?덉뒿?덈떎.', - resolution: '?좎떆 ???ㅼ떆 ?쒕룄?댁<?몄슂. 臾몄젣媛€ 吏€?띾릺硫?遊?愿€由ъ옄?먭쾶 臾몄쓽?섏꽭??', + userMessage: '요청을 처리하는 중 내부 오류가 발생했습니다.', + resolution: '잠시 후 다시 시도해 주세요. 문제가 계속되면 봇 관리자에게 문의해 주세요.', }, E3002: { - userMessage: '?붿껌??泥섎━?섎뒗 以??대? ?ㅻ쪟媛€ 諛쒖깮?덉뒿?덈떎.', - resolution: '?좎떆 ???ㅼ떆 ?쒕룄?댁<?몄슂.', + userMessage: '요청을 처리하는 중 내부 오류가 발생했습니다.', + resolution: '잠시 후 다시 시도해 주세요.', }, E3003: { - userMessage: '紐낅졊?대? ?ㅽ뻾?섎뒗 以??ㅻ쪟媛€ 諛쒖깮?덉뒿?덈떎.', - resolution: '?ㅼ떆 ?쒕룄?댁<?몄슂. 臾몄젣媛€ 吏€?띾릺硫?遊?愿€由ъ옄?먭쾶 臾몄쓽?섏꽭??', + userMessage: '명령을 실행하는 중 오류가 발생했습니다.', + resolution: '다시 시도해 주세요. 문제가 계속되면 봇 관리자에게 문의해 주세요.', }, E3999: { - userMessage: '?덉긽移?紐삵븳 ?ㅻ쪟媛€ 諛쒖깮?덉뒿?덈떎.', - resolution: '?섏쨷???ㅼ떆 ?쒕룄?댁<?몄슂. 臾몄젣媛€ 怨꾩냽?섎㈃ 遊?愿€由ъ옄?먭쾶 臾몄쓽?섏꽭??', + userMessage: '예상치 못한 오류가 발생했습니다.', + resolution: '잠시 후 다시 시도해 주세요. 문제가 계속되면 봇 관리자에게 문의해 주세요.', }, E4001: { - userMessage: 'Discord???섑빐 ?붿껌???쒗븳?섏뿀?듬땲??', - resolution: '?좎떆 湲곕떎由????ㅼ떆 ?쒕룄?댁<?몄슂.', + userMessage: 'Discord에 의해 요청이 제한되었습니다.', + resolution: '잠시 기다린 뒤 다시 시도해 주세요.', }, E4002: { - userMessage: '沅뚰븳 遺€議깆쑝濡?Discord媛€ ?묒뾽??嫄곕??덉뒿?덈떎.', - resolution: '?쒕쾭 愿€由ъ옄?먭쾶 遊뉗쓽 ??븷 諛?梨꾨꼸 沅뚰븳???뺤씤?대떖?쇨퀬 ?붿껌?섏꽭??', + userMessage: '권한 부족으로 Discord가 작업을 거부했습니다.', + resolution: '서버 관리자에게 봇의 역할 및 채널 권한을 확인해 달라고 요청해 주세요.', }, E4003: { - userMessage: 'Discord???쇱떆?곸씤 臾몄젣媛€ 諛쒖깮?덉뒿?덈떎.', - resolution: '?좎떆 ???ㅼ떆 ?쒕룄?댁<?몄슂. 臾몄젣媛€ 吏€?띾릺硫?https://discordstatus.com ?먯꽌 ?곹깭瑜??뺤씤?댁<?몄슂.', + userMessage: 'Discord 측 일시적인 문제가 발생했습니다.', + resolution: + '잠시 후 다시 시도해 주세요. 문제가 계속되면 https://discordstatus.com 에서 상태를 확인해 주세요.', }, }, - // ?€?€ ?먮윭 移댄뀒怨좊━ ?€?댄? ?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€ errorTitles: { USER_INPUT: '입력을 확인해주세요', - PERMISSION: '沅뚰븳??遺€議깊빀?덈떎', - BOT_INTERNAL: '臾몄젣媛€ 諛쒖깮?덉뒿?덈떎', + PERMISSION: '권한이 부족합니다', + BOT_INTERNAL: '문제가 발생했습니다', DISCORD_API: '일시적인 문제입니다.', }, - // ?€?€ ?먮윭 Embed ?꾨뱶 ?쇰꺼 ?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€ errorFields: { resolution: '💡 해결 방법', }, - // ?€?€ ?뚯꽦 梨꾨꼸 ?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€ voice: { channelReady: '{{owner}}, 임시 채널이 준비되었습니다! 아래 드롭다운 메뉴로 관리하세요.', defaultRoomName: '{{username}}의 방', controlPanel: { placeholder: '채널 설정 관리', rename: '채널 이름 변경', - limit: '?몄썝 ?쒗븳 ?ㅼ젙', - lock: '梨꾨꼸 ?좉툑 / ?댁젣', - kick: '?좎? 異붾갑', + limit: '인원 제한 설정', + lock: '채널 잠금 / 해제', + kick: '유저 내보내기', ban: '유저 차단 / 숨기기', - transfer: '?뚯쑀沅??댁쟾', + transfer: '소유권 이전', }, responses: { channelLocked: '채널이 잠겼습니다! 초대된 멤버만 참여할 수 있습니다.', - channelUnlocked: '梨꾨꼸???댁젣?섏뿀?듬땲?? ?꾧뎄??李몄뿬?????덉뒿?덈떎.', - channelRenamed: '梨꾨꼸 ?대쫫??**{{name}}**(??濡?蹂€寃쎈릺?덉뒿?덈떎!', - limitSet: '?몄썝 ?쒗븳??**{{limit}}**紐낆쑝濡??ㅼ젙?섏뿀?듬땲??', + channelUnlocked: '채널 잠금이 해제되었습니다! 누구나 참여할 수 있습니다.', + channelRenamed: '채널 이름이 **{{name}}**(으)로 변경되었습니다!', + limitSet: '인원 제한이 **{{limit}}**명으로 설정되었습니다!', limitUnlimited: '무제한', - kicked: '{{user}}??瑜? 梨꾨꼸?먯꽌 異붾갑?덉뒿?덈떎.', - banned: '{{user}}?먭쾶 梨꾨꼸???④린怨?李⑤떒?덉뒿?덈떎.', - transferPrompt: '梨꾨꼸?????뚯쑀?먮? ?좏깮?섏꽭??', - transferDone: '?뚯쑀沅뚯씠 {{user}}?먭쾶 ?댁쟾?섏뿀?듬땲??', - banPrompt: '李⑤떒?섎㈃ ?대떦 ?좎??먭쾶 梨꾨꼸??蹂댁씠吏€ ?딄쾶 ?⑸땲??', + kicked: '{{user}} 님을 채널에서 내보냈습니다.', + banned: '{{user}} 님에게 채널이 보이지 않도록 차단했습니다.', + transferPrompt: '채널의 새 소유자가 될 사용자를 선택하세요.', + transferDone: '소유권이 {{user}} 님에게 이전되었습니다.', + banPrompt: '차단하면 해당 사용자에게 채널이 보이지 않게 됩니다.', }, }, - // ?€?€ 紐낅졊???€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€ commands: { voiceSetup: { - description: '?꾩떆 ?뚯꽦 梨꾨꼸???꾪븳 ?앹꽦湲?梨꾨꼸???ㅼ젙?⑸땲??', + description: '임시 음성 채널을 위한 생성기 채널을 설정합니다.', setDescription: '기존 음성 채널을 생성기로 설정합니다.', createDescription: '새 음성 채널을 만들고 생성기로 설정합니다.', - channelOptionDescription: '?앹꽦湲곕줈 ?ъ슜???뚯꽦 梨꾨꼸', - categoryOptionDescription: '(?좏깮) ?꾩떆 梨꾨꼸???앹꽦??移댄뀒怨좊━', - nameOptionDescription: '???앹꽦湲??뚯꽦 梨꾨꼸???대쫫', - setSuccess: '{{channel}}??瑜? ?뚯꽦 ?앹꽦湲?梨꾨꼸濡??ㅼ젙?덉뒿?덈떎!', - createSuccess: '{{channel}}??瑜? ?뚯꽦 ?앹꽦湲?梨꾨꼸濡??앹꽦 諛??ㅼ젙?덉뒿?덈떎!', + channelOptionDescription: '생성기로 사용할 음성 채널', + categoryOptionDescription: '(선택) 임시 채널이 생성될 카테고리', + nameOptionDescription: '새 생성기 음성 채널 이름', + setSuccess: '{{channel}}을(를) 음성 생성기 채널로 설정했습니다!', + createSuccess: '{{channel}}을(를) 음성 생성기 채널로 생성·설정했습니다!', }, voiceConfig: { - description: '?쒕쾭???꾩떆 ?뚯꽦 梨꾨꼸 ?ㅼ젙??愿€由ы빀?덈떎.', - setNameTitle: '湲곕낯 ?대쫫 ?쒗뵆由??ㅼ젙', - setNameDesc: '?꾩떆 梨꾨꼸 ?앹꽦 ???ъ슜??湲곕낯 ?대쫫 ?뺤떇???ㅼ젙?⑸땲?? (?ъ슜?먮챸: {{username}})', - setLimitTitle: '湲곕낯 ?몄썝 ?쒗븳 ?ㅼ젙', - setLimitDesc: '?꾩떆 梨꾨꼸 ?앹꽦 ???곸슜??湲곕낯 ?몄썝 ?쒗븳???ㅼ젙?⑸땲??', - statusTitle: '?꾩옱 ?쒕쾭 ?뚯꽦 ?ㅼ젙', + description: '서버의 임시 음성 채널 설정을 관리합니다.', + setNameTitle: '기본 이름 템플릿 설정', + setNameDesc: '임시 채널 생성 시 사용할 기본 이름 형식을 설정합니다. (사용자명: {{username}})', + setLimitTitle: '기본 인원 제한 설정', + setLimitDesc: '임시 채널 생성 시 적용할 기본 인원 제한을 설정합니다.', + statusTitle: '현재 서버 음성 설정', templateLabel: '이름 템플릿', - limitLabel: '湲곕낯 ?몄썝 ?쒗븳', - setSuccess: '?쒕쾭???꾩떆 梨꾨꼸 ?ㅼ젙???낅뜲?댄듃?섏뿀?듬땲??', - limitValue: '{{limit}}紐?(0 = 臾댁젣??', + limitLabel: '기본 인원 제한', + setSuccess: '서버 임시 채널 설정이 업데이트되었습니다.', + limitValue: '{{limit}}명 (0 = 무제한)', }, language: { - description: '遊뉗쓽 ?몄뼱瑜??ㅼ젙?⑸땲??', - scopeDescription: '蹂몄씤?먭쾶留??먮뒗 ?쒕쾭 ?꾩껜???곸슜', - localeDescription: '?ъ슜???몄뼱', - scopeUser: '?섎쭔 ?곸슜', - scopeServer: '?쒕쾭 ?꾩껜 (愿€由ъ옄 ?꾩슜)', - userSet: '媛쒖씤 ?몄뼱媛€ **{{locale}}**(??濡??ㅼ젙?섏뿀?듬땲??', - serverSet: '?쒕쾭 ?몄뼱媛€ **{{locale}}**(??濡??ㅼ젙?섏뿀?듬땲??', - serverPermissionDenied: '?쒕쾭 ?몄뼱 蹂€寃쎌? ?쒕쾭 愿€由ъ옄留?媛€?ν빀?덈떎.', + description: '봇의 언어를 설정합니다.', + scopeDescription: '본인에게만 또는 서버 전체에 적용', + localeDescription: '사용할 언어', + scopeUser: '나만 적용', + scopeServer: '서버 전체 (관리자 전용)', + userSet: '개인 언어가 **{{locale}}**(으)로 설정되었습니다.', + serverSet: '서버 언어가 **{{locale}}**(으)로 설정되었습니다.', + serverPermissionDenied: '서버 언어 변경은 서버 관리자만 할 수 있습니다.', }, event: { - description: '?쒕쾭 ?대깽???쇱젙??愿€由ы빀?덈떎.', - createDescription: '???쒕쾭 ?대깽?몃? ?앹꽦?⑸땲??', - listDescription: '?덉젙???쒕쾭 ?대깽??紐⑸줉??議고쉶?⑸땲??', - cancelDescription: '?덉빟???쒕쾭 ?대깽?몃? 痍⑥냼?⑸땲??', - announceDescription: '?대깽??怨듭? Embed瑜??ㅼ떆 寃뚯떆?⑸땲??', - titleDescription: '?대깽???쒕ぉ', - dateDescription: 'YYYY-MM-DD ?뺤떇???좎쭨', - timeDescription: 'HH:mm ?뺤떇???쒓컙 (24?쒓컙?? Asia/Seoul 湲곗?)', - descriptionOptionDescription: '?좏깮 ?ы빆???대깽???ㅻ챸', - channelDescription: '?좏깮 ?ы빆??怨듭? 梨꾨꼸', - reminderDescription: '由щ쭏?몃뜑 硫붿떆吏€ ?ъ슜 ?щ?', - remindersDescription: '遺??⑥쐞 由щ쭏?몃뜑 紐⑸줉, ?? 0,10,60', - idDescription: '痍⑥냼???대깽??ID', - createSuccessTitle: '?대깽???앹꽦 ?꾨즺', - createSuccessBody: '**{{title}}** ?대깽?멸? ?덉빟?섏뿀?듬땲??', - listTitle: '?덉젙???대깽??紐⑸줉', - listEmpty: '?덉젙???대깽?멸? ?놁뒿?덈떎.', - listItemValue: '**?쒖옉 ?쒓컖:** {{startsAt}}\n**?⑥? ?쒓컙:** {{relative}}\n**?곹깭:** {{status}}\n**由щ쭏?몃뜑:** {{reminder}}\n**梨꾨꼸:** {{channel}}', - cancelSuccess: '`{{id}}` ?대깽?몃? 痍⑥냼?덉뒿?덈떎.', - cancelNotFound: 'ID媛€ `{{id}}`???덉빟 ?대깽?몃? 李얠? 紐삵뻽?듬땲??', - announceSuccess: '`{{id}}` ?대깽?몃? {{channel}} 梨꾨꼸??怨듭??덉뒿?덈떎.', - announceNotAvailable: '???대깽?몄뿉???ъ슜?????덈뒗 怨듭? 梨꾨꼸???ㅼ젙?섏뼱 ?덉? ?딆뒿?덈떎.', - startAnnouncementTitle: '?대깽???쒖옉', - startAnnouncementLead: '???대깽?멸? 吏€湲??쒖옉?⑸땲??', - invalidDateTime: '?대깽???좎쭨 ?먮뒗 ?쒓컙 ?뺤떇???щ컮瑜댁? ?딆뒿?덈떎.', - invalidDateTimeResolution: '?좎쭨??`YYYY-MM-DD`, ?쒓컙?€ `HH:mm` 24?쒓컙 ?뺤떇?쇰줈 ?낅젰?댁<?몄슂.', - invalidReminderOffsets: '由щ쭏?몃뜑 遺??낅젰 ?뺤떇???щ컮瑜댁? ?딆뒿?덈떎.', - invalidReminderOffsetsResolution: '`0,10,60`泥섎읆 0 ?댁긽??遺꾩쓣 ?쇳몴濡?援щ텇???낅젰?댁<?몄슂. 鍮꾩썙?먮㈃ ?먮룞 怨듭????섏? ?딆뒿?덈떎.', - invalidPastDateTime: '怨쇨굅 ?쒓컖?쇰줈 ?대깽?몃? ?덉빟?????놁뒿?덈떎.', - invalidPastDateTimeResolution: '誘몃옒 ?쒓컖???좏깮?????ㅼ떆 ?쒕룄?댁<?몄슂.', + description: '서버 이벤트 일정을 관리합니다.', + createDescription: '새 서버 이벤트를 생성합니다.', + listDescription: '예정된 서버 이벤트 목록을 조회합니다.', + cancelDescription: '예약된 서버 이벤트를 취소합니다.', + announceDescription: '이벤트 공지 Embed를 다시 게시합니다.', + titleDescription: '이벤트 제목', + dateDescription: 'YYYY-MM-DD 형식의 날짜', + timeDescription: 'HH:mm 형식의 시간 (24시간제, Asia/Seoul 기준)', + descriptionOptionDescription: '선택 사항인 이벤트 설명', + channelDescription: '선택 사항인 공지 채널', + reminderDescription: '리마인더 메시지 사용 여부', + remindersDescription: '분 단위 리마인더 목록, 예: 0,10,60', + idDescription: '취소할 이벤트 ID', + createSuccessTitle: '이벤트 생성 완료', + createSuccessBody: '**{{title}}** 이벤트가 예약되었습니다.', + listTitle: '예정된 이벤트 목록', + listEmpty: '예정된 이벤트가 없습니다.', + listItemValue: + '**시작 시각:** {{startsAt}}\n**상대 시간:** {{relative}}\n**상태:** {{status}}\n**리마인더:** {{reminder}}\n**채널:** {{channel}}', + cancelSuccess: '`{{id}}` 이벤트가 취소되었습니다.', + cancelNotFound: 'ID가 `{{id}}`인 예약 이벤트를 찾을 수 없습니다.', + announceSuccess: '`{{id}}` 이벤트를 {{channel}} 채널에 공지했습니다.', + announceNotAvailable: '이 이벤트에는 사용할 수 있는 공지 채널이 설정되어 있지 않습니다.', + startAnnouncementTitle: '이벤트 시작', + startAnnouncementLead: '이 이벤트가 지금 시작됩니다.', + invalidDateTime: '이벤트 날짜 또는 시간 형식이 올바르지 않습니다.', + invalidDateTimeResolution: '날짜는 `YYYY-MM-DD`, 시간은 `HH:mm` 24시간 형식으로 입력해 주세요.', + invalidReminderOffsets: '리마인더 간격 입력 형식이 올바르지 않습니다.', + invalidReminderOffsetsResolution: + '`0,10,60`처럼 0 이상의 분을 쉼표로 구분해 입력해 주세요. 비우면 자동 공지를 사용하지 않습니다.', + invalidPastDateTime: '과거 시각으로는 이벤트를 예약할 수 없습니다.', + invalidPastDateTimeResolution: '미래 시각을 선택한 뒤 다시 시도해 주세요.', statusScheduled: '예약됨', statusCancelled: '취소됨', statusCompleted: '완료됨', - reminderOn: '?ъ슜', + reminderOn: '사용', reminderOff: '사용 안 함', - reminderNone: '?먮룞 怨듭? ?놁쓬', + reminderNone: '자동 공지 없음', announcementChannelNone: '미설정', fields: { - eventId: '?대깽??ID', - startsAt: '?쒖옉 ?쒓컖', - reminder: '由щ쭏?몃뜑', - announcementChannel: '怨듭? 梨꾨꼸', - status: '?곹깭', + eventId: '이벤트 ID', + startsAt: '시작 시각', + reminder: '리마인더', + announcementChannel: '공지 채널', + status: '상태', }, }, music: { - description: 'Play YouTube audio in voice channels.', - addDescription: 'Search YouTube or add a video URL to the queue.', - queueDescription: 'Show the current music queue.', - removeDescription: 'Remove a track from the upcoming queue.', - pauseDescription: 'Pause the currently playing track.', - resumeDescription: 'Resume the paused track.', - skipDescription: 'Skip the currently playing track.', - stopDescription: 'Stop playback and clear the queue.', - leaveDescription: 'Disconnect the bot from the voice channel.', - queryDescription: 'Search query for YouTube', - urlDescription: 'YouTube video URL', - indexDescription: 'Queue index to remove', - addMutuallyExclusive: 'Choose either a search query or a YouTube URL.', - addMutuallyExclusiveResolution: 'Provide exactly one of `query` or `url`.', - notInVoice: 'You must be in a voice channel to use music commands.', - notInVoiceResolution: 'Join a voice channel first, then try again.', - differentVoiceChannel: 'Music is already being used in another voice channel.', - differentVoiceChannelResolution: 'Join the same voice channel as the bot or wait until the current session ends.', - noSearchResults: 'No YouTube results were found for that query.', - noSearchResultsResolution: 'Try a more specific search phrase or use a direct YouTube URL.', - invalidUrl: 'The provided YouTube URL is invalid.', - invalidUrlResolution: 'Use a standard `youtube.com` or `youtu.be` video link.', - noActiveSession: 'There is no active music session in this server.', - noActiveSessionResolution: 'Add a track first to start playback.', - queueAddedNowPlaying: 'Added **{{title}}** and started playback in {{channel}}.', - queueAddedLater: 'Added **{{title}}** to the queue. Position: `#{{position}}`.', - playlistAddedNowPlaying: 'Added **{{count}}** tracks from the playlist and started playback in {{channel}}.', - playlistAddedLater: 'Added **{{count}}** tracks from the playlist to the queue.', - queueTitle: 'Music Queue', - queueEmpty: 'The music queue is currently empty.', - queueNowPlaying: 'Now Playing', - queueUpcoming: 'Up Next', - queueMoreItems: '...and **{{count}}** more track(s).', - queueRemoved: 'Removed **{{title}}** from the queue.', - queueRemoveOutOfRange: 'That queue index does not exist.', - queueRemoveOutOfRangeResolution: 'Use `/music queue` to check the current queue indexes first.', - pauseSuccess: 'Paused the current track.', - resumeSuccess: 'Resumed playback.', - skipSuccess: 'Skipped the current track.', - leaveSuccess: 'Disconnected from the voice channel and cleared the queue.', - stopSuccess: 'Stopped playback and cleared the queue.', - playbackStartedTitle: 'Now Playing', - playbackIdleTitle: 'Queue Finished', - playbackIdleBody: 'There are no more tracks in the queue.', - playbackFailed: 'Failed to play **{{title}}**. Skipping to the next track.', - playbackFailedResolution: 'The stream could not be loaded from YouTube.', - streamUnavailable: 'Could not load a playable audio stream for this video.', - streamUnavailableResolution: 'Try another video or add the track again later.', - requestedBy: 'Requested by', - duration: 'Duration', - progress: 'Progress', - source: 'Source', - status: 'Status', - queueLength: 'Queue Length', - nextTrack: 'Next Track', - statusPlaying: 'Playing', - statusPaused: 'Paused', - unknownDuration: 'Unknown', + description: '음성 채널에서 YouTube 오디오를 재생합니다.', + addDescription: 'YouTube를 검색하거나 영상 URL을 재생 목록에 추가합니다.', + queueDescription: '현재 음악 재생 목록을 표시합니다.', + removeDescription: '대기열에서 곡을 삭제합니다.', + pauseDescription: '현재 재생 중인 곡을 일시정지합니다.', + resumeDescription: '일시정지된 곡의 재생을 다시 시작합니다.', + skipDescription: '현재 재생 중인 곡을 건너뜁니다.', + stopDescription: '재생을 중지하고 대기열을 비웁니다.', + leaveDescription: '봇을 음성 채널에서 내보냅니다.', + queryDescription: 'YouTube 검색어', + urlDescription: 'YouTube 영상 URL', + indexDescription: '대기열에서 삭제할 인덱스', + addMutuallyExclusive: '검색어와 YouTube URL 중 하나만 선택하세요.', + addMutuallyExclusiveResolution: '`query` 또는 `url` 중 정확히 하나만 입력하세요.', + notInVoice: '음악 명령을 사용하려면 음성 채널에 있어야 합니다.', + notInVoiceResolution: '먼저 음성 채널에 참가한 뒤 다시 시도하세요.', + differentVoiceChannel: '다른 음성 채널에서 이미 음악이 재생 중입니다.', + differentVoiceChannelResolution: '봇과 같은 음성 채널에 들어가거나, 현재 세션이 끝날 때까지 기다리세요.', + noSearchResults: '해당 검색어로 YouTube 결과를 찾지 못했습니다.', + noSearchResultsResolution: '검색어를 구체적으로 바꾸거나 YouTube URL을 직접 지정하세요.', + invalidUrl: '제공한 YouTube URL이 올바르지 않습니다.', + invalidUrlResolution: '일반적인 `youtube.com` 또는 `youtu.be` 영상 링크를 사용하세요.', + noActiveSession: '이 서버에 활성 음악 세션이 없습니다.', + noActiveSessionResolution: '먼저 곡을 추가해 재생을 시작하세요.', + queueAddedNowPlaying: '**{{title}}**을(를) 추가하고 {{channel}}에서 재생을 시작했습니다.', + queueAddedLater: '**{{title}}**을(를) 대기열에 추가했습니다. 순번: `#{{position}}`.', + playlistAddedNowPlaying: '플레이리스트에서 **{{count}}**곡을 추가하고 {{channel}}에서 재생을 시작했습니다.', + playlistAddedLater: '플레이리스트에서 **{{count}}**곡을 대기열에 추가했습니다.', + queueTitle: '음악 대기열', + queueEmpty: '대기열이 비어 있습니다.', + queueNowPlaying: '지금 재생 중', + queueUpcoming: '다음 재생', + queueMoreItems: '… 외 **{{count}}**곡 더 있음', + queueRemoved: '대기열에서 **{{title}}**을(를) 제거했습니다.', + queueRemoveOutOfRange: '해당 대기열 번호가 없습니다.', + queueRemoveOutOfRangeResolution: '먼저 `/music queue`로 현재 대기열 번호를 확인하세요.', + pauseSuccess: '현재 곡을 일시정지했습니다.', + resumeSuccess: '재생을 재개했습니다.', + skipSuccess: '현재 곡을 건너뛰었습니다.', + leaveSuccess: '음성 채널에서 나가고 대기열을 비웠습니다.', + stopSuccess: '재생을 중지하고 대기열을 비웠습니다.', + playbackStartedTitle: '지금 재생 중', + playbackIdleTitle: '대기열 종료', + playbackIdleBody: '대기열에 더 이상 곡이 없습니다.', + playbackFailed: '**{{title}}** 재생에 실패했습니다. 다음 곡으로 넘어갑니다.', + playbackFailedResolution: 'YouTube에서 스트림을 불러오지 못했습니다.', + streamUnavailable: '이 영상의 재생 가능한 오디오 스트림을 불러올 수 없습니다.', + streamUnavailableResolution: '다른 영상을 시도하거나 나중에 다시 추가해 보세요.', + requestedBy: '요청자', + duration: '길이', + progress: '진행', + source: '출처', + status: '상태', + queueLength: '대기열 길이', + nextTrack: '다음 곡', + statusPlaying: '재생 중', + statusPaused: '일시정지', + unknownDuration: '알 수 없음', buttons: { - pause: 'Pause', - resume: 'Resume', - skip: 'Skip', - stop: 'Stop', - leave: 'Leave', + pause: '일시정지', + resume: '재개', + skip: '건너뛰기', + stop: '중지', + leave: '나가기', }, }, fishing: { @@ -297,112 +295,113 @@ export const ko: TranslationSchema = { }, permissionAudit: { title: '봇 권한 진단 보고서', - channel: '梨꾨꼸', - noResults: '吏꾨떒??湲곕뒫???놁뒿?덈떎. 遊뉗씠 ?꾩쭅 ?ㅼ젙?섏? ?딆븯?????덉뒿?덈떎.', - summaryLabel: '吏꾨떒 寃곌낵 ?붿빟', - summaryOk: '??紐⑤뱺 ??ぉ ?뺤긽. 臾몄젣媛€ ?놁뒿?덈떎.', - summaryIssue: '??{{fail}}媛??ㅽ뙣 쨌 ?좑툘 {{warn}}媛?寃쎄퀬 媛먯???', - hierarchyWarning: "遊???븷(?쒖쐞: {{botPos}})??'{{role}}'(?쒖쐞: {{targetPos}})蹂대떎 ?꾩뿉 ?덉뼱??愿€由ы븷 ???덉뒿?덈떎.", + channel: '채널', + noResults: '진단할 기능이 없습니다. 봇이 아직 설정되지 않았을 수 있습니다.', + summaryLabel: '진단 결과 요약', + summaryOk: '모든 항목 정상. 문제가 없습니다.', + summaryIssue: '{{fail}}건 실패 · {{warn}}건 경고가 있습니다.', + hierarchyWarning: + "봇 역할(위치: {{botPos}})이 '{{role}}'(위치: {{targetPos}})보다 위에 있어야 해당 역할을 관리할 수 있습니다.", features: { - BASIC: '湲곕낯 遊?湲곕뒫', - VOICE_GLOBAL: '?꾩떆 ?뚯꽦 梨꾨꼸 (?꾩뿭)', - VOICE_GENERATOR_CHANNEL: '?뚯꽦 ?앹꽦湲?梨꾨꼸', - VOICE_GENERATOR_CATEGORY: '?뚯꽦 ?앹꽦湲?移댄뀒怨좊━', - INVITE_TRACKING: '珥덈? 異붿쟻', - INVITE_ROLE_HIERARCHY: '珥덈? ??븷 遺€??(怨꾩링 寃€??', - MIMIC_WEBHOOK: '硫붿떆吏€ ?됰궡 (Webhook)', + BASIC: '기본 봇 기능', + VOICE_GLOBAL: '임시 음성 채널 (전역)', + VOICE_GENERATOR_CHANNEL: '음성 생성기 채널', + VOICE_GENERATOR_CATEGORY: '음성 생성기 카테고리', + INVITE_TRACKING: '초대 추적', + INVITE_ROLE_HIERARCHY: '초대 역할 부여 (계층 검사)', + MIMIC_WEBHOOK: '메시지 흉내 (Webhook)', }, }, setup: { - description: '?ㅼ젙 留덈쾿?щ? ?ㅽ뻾?섏뿬 遊뉗쓽 ?꾩닔 湲곕뒫?ㅼ쓣 ?④퀎蹂꾨줈 ?ㅼ젙?⑸땲??', - step0: { - title: '??遊??ㅼ젙 留덈쾿???쒖옉', - desc: '?섏쁺?⑸땲?? ??留덈쾿?щ? ?듯빐 ?꾨옒 4媛€吏€ ??ぉ???ㅼ젙?⑸땲??\n\n1截뤴깵 **?몄뼱 ?ㅼ젙**\n2截뤴깵 **?꾩닔 沅뚰븳 ?먭?**\n3截뤴깵 **媛먯궗 梨꾨꼸 ?ㅼ젙**\n4截뤴깵 **?꾩떆 ?뚯꽦 梨꾨꼸 ?ㅼ젙**', - startBtn: '?ㅼ젙 ?쒖옉?섍린' + description: '설정 마법사를 실행해 봇의 필수 기능을 단계별로 설정합니다.', + step0: { + title: '봇 설정 마법사 시작', + desc: + '환영합니다! 이 마법사로 아래 4가지 항목을 설정합니다.\n\n1단계 **언어 설정**\n2단계 **필수 권한 확인**\n3단계 **감사 채널 설정**\n4단계 **임시 음성 채널 설정**', + startBtn: '설정 시작하기', }, - step1: { - title: '1截뤴깵 ?몄뼱 ?ㅼ젙', - desc: '?쒕쾭 ?꾩껜???곸슜??遊뉗쓽 湲곕낯 ?몄뼱瑜??좏깮?섏꽭?? (?꾩옱: **{{locale}}**)', + step1: { + title: '1단계 언어 설정', + desc: '서버 전체에 적용될 봇의 기본 언어를 선택하세요. (현재: **{{locale}}**)', placeholder: '언어를 선택하세요', - nextBtn: '?ㅼ쓬 ?④퀎', - skipBtn: '嫄대꼫?곌린' + nextBtn: '다음 단계', + skipBtn: '건너뛰기', }, - step2: { - title: '2截뤴깵 ?꾩닔 沅뚰븳 ?먭?', - descOk: '??**紐⑤뱺 ?꾩닔 沅뚰븳???뺤긽?곸쑝濡?遺€?щ릺???덉뒿?덈떎.**', - descFail: '?좑툘 **?쇰? 沅뚰븳??遺€議깊빀?덈떎.**\n寃곌낵瑜??뺤씤?섍퀬 遊???븷???꾩슂??湲곕뒫 沅뚰븳??遺€?ы빐二쇱꽭??', + step2: { + title: '2단계 필수 권한 확인', + descOk: '**필요한 권한이 모두 부여되어 있습니다.**', + descFail: + '**일부 권한이 부족합니다.**\n결과를 확인하고 봇 역할에 필요한 기능 권한을 부여해 주세요.', recheckBtn: '다시 검사하기', - nextBtn: '?ㅼ쓬 ?④퀎' + nextBtn: '다음 단계', }, - step3: { - title: '3截뤴깵 媛먯궗 梨꾨꼸 ?ㅼ젙', - desc: '遊뉗쓽 二쇱슂 ?대깽?몄? ?먮윭 ?듬낫瑜?諛쏆쓣 梨꾨꼸???좏깮?댁<?몄슂.', - placeholder: '媛먯궗 ?듬낫 梨꾨꼸 ?좏깮', - disableBtn: '媛먯궗 梨꾨꼸 ?꾧린/?댁젣', - nextBtn: '?ㅼ쓬 ?④퀎' + step3: { + title: '3단계 감사 채널 설정', + desc: '봇 이벤트와 오류 로그를 받을 채널을 선택하세요.', + placeholder: '감사 로그 채널 선택', + disableBtn: '감사 로그 끄기', + nextBtn: '다음 단계', }, step4: { - title: '媛먯궗 濡쒓렇 移댄뀒怨좊━ ?ㅼ젙', - desc: '濡쒓렇瑜??섏떊??移댄뀒怨좊━瑜??좏깮?댁<?몄슂.', - nextBtn: '?ㅼ쓬 ?④퀎', + title: '3-1단계 감사 로그 카테고리', + desc: '받을 로그 카테고리를 선택하세요. **초록**은 켜짐, **빨강**은 꺼짐입니다.', + nextBtn: '다음 단계', }, - step5: { - title: '4截뤴깵 ?꾩떆 ?뚯꽦 梨꾨꼸 ?ㅼ젙', - desc: '?꾩떆 ?뚯꽦 梨꾨꼸???앹꽦??"?앹꽦湲?梨꾨꼸"???좏깮?댁<?몄슂.\n湲곗〈??梨꾨꼸??怨좊Ⅴ嫄곕굹 移댄뀒怨좊━/梨꾨꼸??遊뉗씠 **?먮룞 ?앹꽦**?섍쾶 ???섎룄 ?덉뒿?덈떎.', - placeholder: '?앹꽦湲곕줈 ???뚯꽦 梨꾨꼸 ?좏깮', - autoBtn: '?? ?먮룞 ?앹꽦?섍린', - skipBtn: '?꾩떆 ?뚯꽦 ?ъ슜 ?덊븿', - nextBtn: '?ㅼ젙 ?꾨즺' + step5: { + title: '4단계 임시 음성 채널 설정', + desc: + '임시 음성 채널의 「생성기 채널」을 선택하세요.\n기존 채널을 고르거나 카테고리/채널을 봇이 **자동 생성**하게 할 수도 있습니다.', + placeholder: '생성기로 쓸 음성 채널 선택', + autoBtn: '자동 생성하기', + skipBtn: '임시 음성 사용 안 함', + nextBtn: '설정 완료', }, - step6: { - title: '?럦 ?ㅼ젙 ?꾨즺 ?붿빟', - desc: '**1. ?몄뼱**: {{lang}}\n**2. 媛먯궗 梨꾨꼸**: {{audit}}\n**3. 媛먯궗 移댄뀒怨좊━**: {{categories}}\n**4. ?꾩떆 ?뚯꽦 梨꾨꼸**: {{voice}}', - finishBtn: '마치기' + step6: { + title: '설정 요약', + desc: '**1. 언어**: {{lang}}\n**2. 감사 채널**: {{audit}}\n**3. 감사 카테고리**: {{categories}}\n**4. 임시 음성 채널**: {{voice}}', + finishBtn: '마치기', }, - finished: '???ㅼ젙 留덈쾿?щ? 醫낅즺?덉뒿?덈떎.', - expired: '???쒓컙??留뚮즺?섏뿀?듬땲?? `/setup`???ㅼ떆 ?ㅽ뻾?댁<?몄슂.', - defaultCategoryName: '?뚯꽦 梨꾨꼸', - defaultGeneratorName: '??梨꾨꼸 ?앹꽦?섍린', + finished: '설정 마법사를 완료했습니다.', + expired: '시간이 만료되었습니다. `/setup`을 다시 실행해 주세요.', + defaultCategoryName: '음성 채널', + defaultGeneratorName: '채널 생성하기', auditCategories: { SYSTEM: '시스템', BOOT: '부팅', - VOICE: '?뚯꽦', - PERMISSION: '沅뚰븳', - INVITE: '珥덈?', + VOICE: '음성', + PERMISSION: '권한', + INVITE: '초대', }, }, config: { - title: '湲곕뒫 ?ㅼ젙 蹂€寃?寃곌낵', - noOptions: '蹂€寃쏀븷 ?듭뀡???섎굹 ?댁긽 ?좏깮?댁<?몄슂.', + title: '기능 설정 변경 결과', + noOptions: '변경할 옵션을 하나 이상 선택해 주세요.', mimic: { - label: '誘몃?(Mimic)', + label: '흉내(Mimic)', enabled: '활성화', - disabled: '鍮꾪솢?깊솕', + disabled: '비활성화', }, emoji: { - label: '?대え吏€ ?뺣?(Big Emoji)', + label: '큰 이모지(Big Emoji)', enabled: '활성화', - disabled: '鍮꾪솢?깊솕', + disabled: '비활성화', }, }, }, - // ?€?€ 紐⑤떖 ?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€ modals: { renameTitle: '음성 채널 이름 변경', - renameLabel: '??梨꾨꼸 ?대쫫', - limitTitle: '?몄썝 ?쒗븳 ?ㅼ젙', - limitLabel: '?몄썝 ?쒗븳 (0 = 臾댁젣?? 1-99)', + renameLabel: '새 채널 이름', + limitTitle: '인원 제한 설정', + limitLabel: '인원 제한 (0 = 무제한, 1–99)', }, - // ?€?€ ?€?됲듃 硫붾돱 ?뚮젅?댁뒪?€???€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€ selects: { kickUser: '추방할 유저를 선택하세요', banUser: '차단할 유저를 선택하세요', transferOwner: '소유권을 이전할 유저를 선택하세요', }, - // ?€?€ ?곹깭 硫붿떆吏€ ?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€ presence: { servers: '{{guildCount}}개의 서버에서 작동 중', help: '/help 명령어를 확인하세요', @@ -410,6 +409,3 @@ export const ko: TranslationSchema = { version: 'Kord v1.0.0', }, }; - - - diff --git a/src/services/FishingService.ts b/src/services/FishingService.ts index 6edfaf3..25587b6 100644 --- a/src/services/FishingService.ts +++ b/src/services/FishingService.ts @@ -1,4 +1,4 @@ -import { +import { ActionRowBuilder, AttachmentBuilder, ButtonBuilder, diff --git a/src/services/MusicService.ts b/src/services/MusicService.ts index a2a2f69..23658d8 100644 --- a/src/services/MusicService.ts +++ b/src/services/MusicService.ts @@ -1,4 +1,4 @@ -import { +import { ActionRowBuilder, ButtonBuilder, ButtonInteraction, diff --git a/tests/services/FishingService.test.ts b/tests/services/FishingService.test.ts index 11eaff6..36c6e2f 100644 --- a/tests/services/FishingService.test.ts +++ b/tests/services/FishingService.test.ts @@ -1,4 +1,4 @@ -import { buildFishingGauge, buildFishingLane } from '../../src/services/FishingService'; +import { buildFishingGauge, buildFishingLane } from '../../src/services/FishingService'; describe('FishingService helpers', () => { it('renders a gauge with filled and empty segments', () => {