Skip to content

feat(translate): Add automatic message translation to player language#335

Open
SolverNA wants to merge 51 commits into
Flectone:masterfrom
SolverNA:master
Open

feat(translate): Add automatic message translation to player language#335
SolverNA wants to merge 51 commits into
Flectone:masterfrom
SolverNA:master

Conversation

@SolverNA

Copy link
Copy Markdown

Summary

This PR implements automatic message translation based on player's client language settings. Messages are now automatically translated to each player's locale, with an option to toggle between original and
translation.

Changes

  • TranslatedMessage model: Stores original text and all translations by locale
  • Auto-translation system: Translates messages once per unique locale (not per player)
  • Toggle command: /toggleoriginal to switch between original and translated message
  • Optimized API usage: 100 players with 4 languages = 4 API requests (not 100!)
  • Updated localizations: Button now toggles original/translation instead of creating new message

How it works

  1. Player sends message in their language (e.g., "Hello" in en_us)
  2. System detects all unique locales on server (e.g., ru_ru, de_de, fr_fr)
  3. Translates message once per locale (3 API requests total)
  4. Each player receives message in their language
  5. Click button on message to see original text

Configuration

Requires language.by-player: true in config.yml and translation API setup (Google/Yandex/DeepL).

Testing

Tested with multiple players using different client languages. Translation and toggle functionality working as expected.

Note

Paper/Bukkit module has pre-existing compilation errors (missing Paper-specific classes). Core module compiles successfully. Fabric, BungeeCord, and Velocity versions work perfectly.

@TheFaser

Copy link
Copy Markdown
Member

Разве такой автоматический перевод не добавляет задержку в чат у сообщений?

@SolverNA

Copy link
Copy Markdown
Author

Да сег сделаю асинхронный перевод

@TheFaser

Copy link
Copy Markdown
Member

Так разве всёравно не будет задержки в сообщениях? Ведь после отправки сообщения оно должно быть переведено через API, а это даст задержку в отправлении сообщения

@SolverNA

Copy link
Copy Markdown
Author

Нет, мы не будем делать одним потоком всё. Сообщения отправляем как обычно оригиналом, и асинхронно отправляем запрос на перевод, перевод приходит, изменяем сообщение на переведённое.

@TheFaser

Copy link
Copy Markdown
Member

Идею я твою понял, как будет время я посмотрю твой PR, некоторые моменты можно упростить

SolverNA added 3 commits May 13, 2026 17:59
  - Created TranslatedMessage model to store all message translations
  - Extended HistoryMessage to support original/translation toggle
  - Added /toggleoriginal command to switch between original and translation
  - Implemented automatic translation to all unique server locales (API request optimization)
  - Updated localizations: button now toggles original/translation instead of creating new message
  - Added PulseAutoTranslateListener to handle auto-translation in MessagePrepareEvent
  - Modified DeleteModule to work with translations and toggling

  Now each message is automatically translated to the receiver's language.
  Button on message allows switching between original and translation.
  - Created TranslationCacheService with MyMemory API integration
  - Added TRANSLATION_CACHE (24h, 50000 entries) for persistent translation storage
  - Integrated MyMemory API as fallback before main translation service
  - Converted translateToAllLocales to async with CompletableFuture for parallel translations
  - Added useMyMemory config option (default: true)
  - Added MYMEMORY service type to translation services
  - Updated configs for minecraft and hytale platforms

  Benefits:
  - No chat delay: original message sent immediately, translations load in background
  - MyMemory provides free cached translations (10k requests/day)
  - Parallel translation requests instead of sequential
  - Local cache reduces API calls for repeated phrases
  - ToggleOriginalModule: use registerCustomCommand instead of registerCommand
    so /toggleoriginal is registered as a top-level command
  - DeleteModule: prevent double-save of the same message UUID in history
  - PulseAutoTranslateListener: replace ConcurrentHashMap with 30s Guava Cache
    so all receivers get translation, not just the first one;
    fix unsafe FEntity cast with instanceof check
  - TranslationCacheService: recreate ExecutorService on shutdown() so async
    translations survive plugin reload; reduce thread pool from 10 to 4
  - TranslateModule: remove dead Map<UUID,TranslatedMessage> (memory leak)
@TheFaser

Copy link
Copy Markdown
Member

Меня пока что смущает, что ты делаешь это с помощью ИИ, хоть он и понимает контекст проекта, но в некоторых местах избыточные проверки

public String translate(FPlayer fPlayer, String source, String target, String text) {
return switch (config().service()) {
// Check cache first if MyMemory is enabled
if (config().useMyMemory() != null && config().useMyMemory()) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

проверка на null бесполезна, оно всегда будет существовать, если вписано в файл конфигурации

private final MessageDispatcher messageDispatcher;
private final ModuleController moduleController;
private final ModuleCommandController commandModuleController;
private final net.flectone.pulse.service.TranslationCacheService translationCacheService;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

зачем указывать полный путь к классу?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

меня тоже бесит эта фигня в иишках)

@SolverNA

Copy link
Copy Markdown
Author

я крч хотел с ии набросать а потом подправить сейчас крч довожу потом буду патчить

@TheFaser

Copy link
Copy Markdown
Member

я крч хотел с ии набросать а потом подправить сейчас крч довожу потом буду патчить

В целом мне твоя идея нравится, но тебе нужно это доработать.

  1. Удали хранение переводов в облаке, оставь только в кэше
  2. Я не думаю, что стоит добавлять прям отдельную команду для этого, почему бы не сделать /translateto toggle вариант у команды? Просто проверять перед переводом значение
  3. Не знаю зачем, но ты пытаешься что-то делать с Delete модулем, это бесполезно т.к. Delete модуль самостоятелен и должен работать без внешних вмешательств

Comment on lines +11 to +46
public record HistoryMessage(
UUID uuid,
Component component,
@Nullable TranslatedMessage translatedMessage,
boolean showOriginal
) {

public HistoryMessage(UUID uuid, Component component) {
this(uuid, component, null, false);
}

public HistoryMessage(UUID uuid, Component component, TranslatedMessage translatedMessage) {
this(uuid, component, translatedMessage, false);
}

/**
* Get the component to display based on showOriginal flag and player locale.
*/
public Component getDisplayComponent(String playerLocale) {
if (translatedMessage == null) {
return component;
}

if (showOriginal) {
return translatedMessage.getOriginal();
}

return translatedMessage.getTranslation(playerLocale);
}

/**
* Check if this message has translations.
*/
public boolean hasTranslations() {
return translatedMessage != null;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

мне до сих пор не понятно, зачем ты трогаешь delete модуль, если он никак не связан с твоей идеей. Он будет правильно отрабатывать в любом случае без всех этих проверок, потому что сообщения приходят пользователю через пакеты, которые FlectonePulse слушает

DEEPL,
GOOGLE,
YANDEX
YANDEX,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

зачем useMyMemory и отдельный сервис MYMEMORY?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

кажется я понял, ты сохраняешь переведённые сообщения в облаке, чтобы их не переводить, но у этого есть одно но: у тебя в любом случае это HTTP запрос, который занимает время и смысла от того, что ты это сохраняешь в облаке нет + сообщения редко повторяться будут.

Хранить переводы в облаке плохая идея, нужно только локально в кэше это делать, rate limit сложно будет получить от API перевода

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mymemory это сервис где есть уже готовые переводы для коротких фраз какбы онлайн кеш и мы вначале обрашаемся в локальный кеш потом в mymemory и уже потом запрос на перевод через api переводчиков

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mymemory это сервис где есть уже готовые переводы для коротких фраз какбы онлайн кеш и мы вначале обрашаемся в локальный кеш потом в mymemory и уже потом запрос на перевод через api переводчиков

Так а смысл, это тоже HTTP запрос, который будет задерживать отправление сообщения. Перевод текста моментальный от API, максимум можно бояться rate limit, но этого сложного достичь

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Можешь, конечно, реализацию эту оставить, но тогда её не нужно вписывать как enum, оставь только булеан

}
in.close();

// Parse JSON response: {"responseData":{"translatedText":"..."}}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

в проекте есть gson, можно создать обёртку record и использовать gson.fromJson без костыльных парсингов

@TheFaser

TheFaser commented May 13, 2026

Copy link
Copy Markdown
Member

Гитхаб наспамил, ща пофиксим, во, норм

SolverNA added 2 commits May 14, 2026 14:23
event.withMessage(translatedComponent) replaced the fully formatted
component (player nickname + colors + ⇄ button) with Component.text(plain_text),
causing the chat to display a plain, unformatted message.

Now we save event.message() (full format) to the translations history
for toggleOriginal to work without affecting the message itself.
…ine.

Logs at every stage for diagnosing why translation isn't working:
- PulseAutoTranslateListener: PrepareEvent/SendEvent (inputs, skip reasons, cache miss/hits)
- TranslateModule: translateToAllLocales (online players, locales, async startup), addTag (button resolver)
- TranslationCacheService: HTTP requests to MyMemory (URL, code, body), cache GET/PUT, async tasks
- PulseTranslateListener: FormattingEvent with flag parsing

[AutoTranslate] and [Translate] prefixes for convenient grepping in server.log.
@SolverNA

SolverNA commented May 14, 2026

Copy link
Copy Markdown
Author

Привет! Разобрался, зачем ИИ использовал deleteModule.

Я изначально думал, что плагин умеет изменять существующие сообщения в чате, и хотел использовать эту возможность так: оригинальное сообщение после асинхронного перевода заменяется на переведённое, а кнопка «Показать оригинал» — возвращает его обратно. Но посмотрел, как плагин работает на самом деле, и оказалось, что перевод приходит отдельным сообщением, а не редактирует существующее. Моя же идея была именно в изменении сообщения прямо в истории чата.


Архитектура, которую предложил ИИ:

Создаём ChatHistoryService, который хранит историю чата. Для эмуляции «редактирования» сообщения делаем следующее:

  1. Находим нужное сообщение в истории.
  2. Подменяем его текст (оригинал ↔ перевод).
  3. Переотправляем игроку историю чата вплоть до конца истории чата.

Это создаёт эффект изменения сообщения на клиенте, хотя технически это переотправка истории.

Дополнительно — для оптимизации храним не полную историю для каждого игрока, а только пометки: должен ли конкретный игрок «видеть» то или иное сообщение в оригинале или переводе. Принцип похож на то, как устроен Git — полная история общая, а состояние для каждого игрока восстанавливается по этим меткам.


Есть небольшие технические вопросы:

  • Будет ли переотправка истории заметна игрокам визуально?
  • Насколько это корректный подход с архитектурной точки зрения?
  • Не создаст ли это нагрузку на сервер при множественной переотправки истории игрокам?

@SolverNA

Copy link
Copy Markdown
Author

Структурировал через ИИ, чтобы было понятно. Мой текст просто оставляет желать лучшего.

@TheFaser

Copy link
Copy Markdown
Member

В майнкрафте нельзя изменять отправленное сообщение, потому что на клиенте это просто отрендеренный текст и там нет динамики, это как txt файл, только в красивой обёртке.

Идея тоже прикольная, чтобы "изменять" переведённое сообщение, но не будет ли это слишком нагружать и сервер, и клиента не знаю. Если ты правда хочешь это довести до конца, то тебе не нужно изменять DeleteModule, ты должен реализовать свой механизм, который можешь по аналогии скопировать из delete модуля, потому что FlectonePulse имеет модульную структуру и каждые модули независимы друг от друга

SolverNA added 7 commits May 14, 2026 16:38
  Move per-receiver chat history (playersHistory, cachedComponents) and the
  brute-force chat replay mechanism (sendUpdate) out of DeleteModule into a
  new shared ChatHistoryService.

  DeleteModule keeps its delete-specific logic (addTag, remove, toggleOriginal)
  but delegates all history storage and replay operations to ChatHistoryService.
  Public API is preserved — PulseDeleteListener, PulseAutoTranslateListener and
  ToggleOriginalModule continue to compile and behave identically.

  This sets up decoupling for the auto-translate feature in the follow-up
  commit: TranslateModule will use ChatHistoryService directly instead of
  reaching into DeleteModule (which was a layering violation flagged by the
  plugin author).
  Per plugin author's request: keep modules self-contained, no cross-module
  dependencies. The previous translate feature reached into DeleteModule
  (save/sendUpdate/toggleOriginal) which violated the plugin's modular design.

  Reverts the following author-owned files to caa9551:
    - DeleteModule, HistoryMessage (delete/model)
    - TranslateModule, PulseTranslateListener
    - TranslatetoModule, Command, CommandModule
    - YAML configs and localizations (translate.action button restored to
      author's /translateto idiom, /toggleoriginal command removed)

  Removes ChatHistoryService (introduced in fedfc9c) — author rejected the
  shared-service approach; each module must own its state.

  Removes user-created files that depended on the removed extensions:
    - ToggleOriginalModule (called deleteModule.toggleOriginal)
    - PulseAutoTranslateListener (called translateModule.translateToAllLocales
      and deleteModule.save with translatedMessage)
    - TranslatedMessage record

  Preserved for the self-contained rebuild in follow-up commits:
    - TranslationCacheService (standalone HTTP + cache, plus the %7C URL fix)
    - CacheName.TRANSLATION_CACHE and ModuleName.COMMAND_TOGGLEORIGINAL
      (1-line additions needed for the rebuild)
  TranslateModule now owns the full auto-translate flow without depending on
  DeleteModule. Chat history mechanism is copied by analogy (per module author's
  guidance: modules must stay independent, duplicate code over shared state).

  Flow:
    1. Player writes — original is dispatched immediately to all receivers (no
       blocking, no delay).
    2. PulseAutoTranslateListener.onMessagePrepareEvent kicks off async translation
       for every unique online locale other than the sender's, via
       TranslationCacheService.translateWithMyMemoryAsync (chain comes next commit).
    3. PulseAutoTranslateListener.onMessageSendEvent records the original formatted
       component into TranslateModule's own per-receiver history together with the
       TranslatedMessage that will be populated by callbacks.
    4. When each async translation lands, TranslateModule.replayForLocale triggers
       a chat redraw for every online receiver whose locale matches — they had
       the original; now they see the translation.

  Per-receiver behavior:
    - Same locale as sender → no translation, no toggle button (nothing to toggle).
    - Different locale → sees translation after async lands, button toggles to
      original via /toggleoriginal <message-uuid>.
    - Console/non-player sender → no button (auto-translate only handles player chat).

  ToggleOriginalModule recreated to call TranslateModule.toggleOriginal directly —
  no DeleteModule reference. Reuses Deletemessage command config (cooldown / aliases
  / permission) since the plugin has no dedicated config record for this command.

  YAML: translate.action button now runs /toggleoriginal <message> (en_us and
  ru_ru localizations) — replaces the author's original /translateto idiom for
  the auto-translate variant of the feature.

  History length is borrowed from delete module config — both modules push out
  the same number of empty newlines for the brute-force redraw.
  Startup crash: CacheRegistry iterates every CacheName enum value and looks
  up its config in cache().types(). The TRANSLATION_CACHE enum value survived
  the revert (it's needed by TranslationCacheService) but the corresponding
  config.yml stanza was lost when author's resource files were restored.

  Re-add the same TRANSLATION_CACHE block originally introduced in ccccadc:
    invalidate_on_reload: false
    duration: 24 HOURS
    size: 50000

  Applied to both minecraft and hytale default configs.
…howing as \uXXXX

  MyMemory returns non-ASCII characters as JSON unicode escapes (e.g. Cyrillic
  'и' arrives as the literal 5 chars \u0438). The hand-rolled JSON parser
  only handled \n \t \\ \" escapes — \uXXXX fell through unchanged, so
  the cache and chat ended up showing the raw escape sequences.

  Replaced the manual indexOf-based parser with Gson, which handles all JSON
  string escapes uniformly. Also drops ~70 lines of custom parsing/escaping
  code.

  Project already pulls in Gson 2.8.0 as a shaded dep, so used the older
  'new JsonParser().parse(...)' API for compatibility (parseString static
  method was added in 2.8.6).
…rillic correctly

  Two related fixes that surfaced together during testing:

  1) JSON unicode escape — MyMemory returns non-ASCII as \uXXXX in the JSON
     payload (Cyrillic, CJK, etc.). The hand-rolled parser only handled \n
     \t \\ \" escapes, so translations like 'и' arrived as the literal 6
     chars '\u0438'. Replaced the custom parser with Gson (already a project
     dep) which decodes every JSON string escape uniformly.

  2) Plain-text replay — after a translation landed, sendUpdate redrew chat
     using Component.text(translation), losing the chat format (nickname,
     colors, ⇄ button). Now translations are kept as plain strings inside
     TranslatedMessage; the rendered component is produced lazily in
     TranslateHistoryMessage.getDisplayComponent via Component.replaceText —
     swap original text literal with translation inside the existing component
     tree. No pipeline rebuild needed; all surrounding formatting stays intact.

  TranslateHistoryMessage now carries the raw player text alongside the
  formatted component so replaceText has a literal to match. PulseAutoTranslateListener
  passes eventMetadata.message() into save() for this purpose.

  Map type in TranslatedMessage.translations changed from <String,Component>
  to <String,String> — Components are no longer stored, only the text strings.
…n via config

  MyMemory has been returning poor translations during testing — e.g.
  'привет как дела' → 'salam necesen' (Azerbaijani), short English phrases
  echoed back unchanged, plus a 1000 word/day per-IP rate limit. Switch the
  default auto-translate provider to Google Translate's free public gtx
  endpoint (no API key required).

  Config: message.format.translate.use_my_memory (default false).
    - false → Google Translate (translate.googleapis.com/translate_a/single)
    - true  → MyMemory (kept available for users who prefer it)

  TranslationCacheService now exposes translateWithGoogleAsync alongside the
  existing translateWithMyMemoryAsync; both share a small submitAsync helper
  that handles cache lookup, executor submission, and post-success cache PUT.
  Google response parsed with Gson — the gtx endpoint returns a nested array
  [[['translated','original',null,null,1]],null,'en',...]; we concatenate the
  first item of every segment for multi-sentence translations.

  Also: skip the chat redraw when translation comes back equal to the
  original — saves a useless replay for messages MyMemory/Google can't
  translate (mostly proper nouns and short phrases). The toggle button stays
  visible (it's baked into the formatted component at build time) but
  clicking it is a visual no-op in this case, since both states show the
  same text.
SolverNA added 3 commits May 15, 2026 19:02
  Root cause of phantom 'text=' history entries appearing after every chat:
  onMessageSendEvent saved originalComponent into selfOriginatedComponents
  (via save's needToCache=true). But when a cached translation was applied,
  event.withMessage(translatedComponent) made the outgoing packet carry
  translatedComponent — a different Component instance. The packet's
  PacketSendEvent → MessageReceiveEvent fired with translatedComponent, but
  isCached() compares by reference and only found originalComponent → dedup
  silently failed → onMessageReceiveEvent saved a second history entry with
  empty originalText.

  empty originalText.

  Fix:
    - New TranslateModule.markSelfOriginated(Component) — registers a
      Component for ReceiveEvent dedup separately from save().
    - onMessageSendEvent now calls save(...) with needToCache=false (history
      stores originalComponent for the showOriginal toggle path) and then
      markSelfOriginated(event.message()) AFTER any withMessage(...) — so
      whatever variant the packet actually carries is the one we register.

  For the cache-miss path (no withMessage applied) event.message() ==
  originalComponent, so behavior is unchanged.

  Identified by analysing PulseDeleteListener: the author's module doesn't
  modify event.message() between save() and the packet send, so its
  cached/sent components are always identical and dedup just works. Our
  translation-applying listener needed the extra step.
…p-skipped and system messages

  Previously the listener bailed out of save() whenever preparedTranslations
  didn't contain a TranslatedMessage for the current messageUUID. Two real-world
  chat events have a null TranslatedMessage and were silently dropped from the
  history:

    1. Dedup-skipped rapid repeats. PrepareEvent's 1-second per-(sender,text)
       dedup cache prevents the second through Nth same-content chat from
       populating preparedTranslations. The SendEvent still fires for each
       receiver, but with my previous code skipped save() because
       translatedMessage was null. Net effect: a player who typed 'hello' six
       times in two seconds got one history entry; replay/redraw on toggle
       showed only that one line, losing five visible chat events.

    2. System messages — join, quit, /give output, plugin announcements that
       dispatch through messageDispatcher with a non-FPlayer sender. PrepareEvent
       bails out at the FPlayer instanceof check, no TranslatedMessage is ever
       created, and the SendEvent listener used to skip save(). Chat redraw
       pushed these off-screen with the brute-newline spam.

  Fix: save() runs unconditionally now. For the dedup-skipped path where the
  cache HIT for this receiver's locale, the listener synthesizes a
  TranslatedMessage on the fly (originalLang + receiverLocale entries
  populated) so toggle works on those entries too. System messages save with
  translatedMessage=null — they show as original in any redraw and are not
  toggleable, which is correct since there's nothing to switch between.

  needToCache stays false at the save call: the outgoing packet's dedup
  component is registered separately via markSelfOriginated(event.message())
  right after, capturing the result of any prior withMessage(translatedComponent)
  substitution. ReceiveEvent on the echo-back sees the exact reference and
  skips, no duplicate entries.
  Bug surfaced as half the history entries having translationForLocale=<none>
  in sendUpdate logs even though the cache had a valid translation. Clicking
  toggle on those entries logged 'has no translations (server/system message)'
  and did nothing visually — user thought toggle was 'lagging'.

  Root cause: save() runs once per receiver, and per-receiver behavior in
  PulseAutoTranslateListener.onMessageSendEvent diverges based on whether
  receiverLocale == sourceLang. For the SAME-locale receiver (the sender's own
  view) the listener never enters the cache-lookup branch and passes a null
  TranslatedMessage to save(). For the DIFFERENT-locale receiver it synthesizes
  one. Save order isn't guaranteed: when the same-locale save runs first it
  CREATEs the entry with a null TM; the subsequent UPDATE path silently
  discarded the incoming non-null TM. Half the entries stayed un-toggleable.

  Fix: in the UPDATE branch, if existing.translatedMessage is null but the
  incoming one isn't, replace the entry via withTranslatedMessage (records
  are immutable; list.set keeps the global order intact). If both are non-null
  the incoming translations map is merged via putAll — keeps any per-locale
  entries the existing one already has and adds new ones.

  This matches the user's intended architecture: the global history *holds*
  the translations alongside originalText and viewers, rather than relying on
  the global cache as the sole source of truth at render time.
@SolverNA

Copy link
Copy Markdown
Author

Привет! Я реализовал всё, что планировал: сообщения теперь переводятся полностью автоматически, и переключение между оригиналом и переводом изменяет только текст сообщения.

Также реализовал задумку с сохранением общей истории полученных сообщений: теперь к каждому сообщению присваиваются получатели, благодаря чему история чата восстанавливается для каждого игрока индивидуально.

Можешь проверить. Единственный нюанс текст над головой пока не переводятся. Это отдельный модуль, и для его асинхронного перевода придётся сильно изменять логику модуля.

@TheFaser TheFaser left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ещё в целом старайся делать меньше комментариев и java doc, здесь это не очень уместно, потому что нет стандарта сейчас у меня, который я бы придерживался для обычных модулей. Максимум можешь оставить комменты для понимания происходящего внутри методов, но не после каждого слова

Comment on lines +82 to +83
fLogger.debug("[AutoTranslate] PrepareEvent: skip uuid=%s — duplicate of recent message (sender=%s text='%s')",
messageUUID, sender.name(), message);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

я не думаю, что debug имеет смысл, слишком много спама в консоль, даже учитывая, что он только при включённом флаге. Я не реализовывал систему дебага впринципе, потому что не вижу в этом большого смысла

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

я просто отстраивал систему на дебаге ну если не нужно то могу убрать или сократить

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

я просто отстраивал систему на дебаге ну если не нужно то могу убрать или сократить

Сейчас лучше убрать дебаг полностью, над ним нужно думать отдельно, как о глобальной системе в проекте

Comment on lines +55 to +65
private final List<TranslateHistoryMessage> globalHistory = new java.util.ArrayList<>();

/**
* Per-player toggle state: which message UUIDs that player has flipped to
* "show original". Stored separately from the history entries because it's
* per-player; the entries themselves are shared.
*/
private final Map<UUID, Set<UUID>> playerOriginalToggles = new ConcurrentHashMap<>();

/** Components originated by FlectonePulse itself — dedup against MessageReceiveEvent. */
private final List<Component> selfOriginatedComponents = new CopyOnWriteArrayList<>();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

зачем это здесь? Тебе нужна только 1 мапа, где будет ключом UUID - receiver, а значением сообщение готовое, тут ты слишном намудрил

Comment on lines +75 to +76
String urlString = "https://api.mymemory.translated.net/get?q=" + encodedText
+ "&langpair=" + normalizedSource + "%7C" + normalizedTarget;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

должна быть константа для url

URI uri = new URI(urlString);
HttpURLConnection connection = (HttpURLConnection) uri.toURL().openConnection();
connection.setRequestMethod("GET");
connection.setRequestProperty("User-Agent", "FlectonePulse/1.9.4");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

есть WebUtil, там указан User Agent

Comment on lines +128 to +130
duration: 24
time_unit: "HOURS"
size: 50000

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

24 часа? это слишком много. Кэш создан чтобы удаляться через время и не занимать память просто так, а ты по сути из кэша превратил его в обычную мапу, 1 час максимум

Comment on lines +276 to +283
# auto: true → async pre-translation + /toggleoriginal button
# auto: false → author's classic /translateto click-to-translate mode
auto: true
providers:
- GOOGLE
- MYMEMORY
- DEEPL
- YANDEX

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

зачем? это указывается в команде tranlateto, для чего это дублирование?

Comment on lines +649 to +651
action: " <click:run_command:\"/toggleoriginal <message>\"><hover:show_text:\"<fcolor:2>Show original/translation\"><fcolor:1><sprite_or:⇄:gui:icon/language>"
# Used when message.format.translate.auto: false (classic /translateto mode)
action_manual: " <click:run_command:\"/translateto <language> <language> <message>\"><hover:show_text:\"<fcolor:2>Translate message\"><fcolor:1><sprite_or:⇄:gui:icon/language>"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

над названием параметров нужно точно подумать, должно быть понятно что это за формат

Comment on lines +534 to +541
synchronized (globalHistory) {
for (TranslateHistoryMessage e : globalHistory) {
if (e.uuid().equals(messageUUID)) {
entry = e;
break;
}
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

как я написал выше, тебе нужна только одна мапа, без этого листа


/** True when message.format.translate.auto is enabled (default). */
private boolean isAutoMode() {
return !Boolean.FALSE.equals(translateModule.config().auto());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

просто проверяй translateModule.config().auto() без всяких Boolean.FALSE/TRUE, это нейронка и IDEA думают, что они могут быть null, но нет, они не могут быть такими, иначе плагин не включится

SolverNA added 18 commits June 13, 2026 15:32
…ialService.getSetting, MessagePipeline.resolver/build, CommandRegistry)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants