Skip to content

feat(bouncer-networks): protocol + store foundation (1/3)#214

Open
ValwareIRC wants to merge 15 commits into
mainfrom
bouncer-networks/protocol
Open

feat(bouncer-networks): protocol + store foundation (1/3)#214
ValwareIRC wants to merge 15 commits into
mainfrom
bouncer-networks/protocol

Conversation

@ValwareIRC
Copy link
Copy Markdown
Contributor

@ValwareIRC ValwareIRC commented May 14, 2026

Part 1 of 3 for soju.im/bouncer-networks support. Headless — no UI yet.

What this PR is

Implements the protocol parser and store reducer for soju.im/bouncer-networks. Lays the foundation for a single bouncer endpoint to expose multiple upstream networks to one ObsidianIRC client.

Scope

What Where
Attribute codec (mtag-style key=value;… with \\s \\: \\\\ \\r \\n escapes) src/lib/bouncerAttrs.ts
BOUNCER command dispatch + FAIL BOUNCER per-code decoding src/lib/irc/handlers/bouncer.ts
BouncerNetwork / BouncerState types src/types/index.ts
Store reducer (CAP detection, ISUPPORT BOUNCER_NETID, BATCH-end listed flag, network merge / delete / FAIL stashing) src/store/handlers/bouncer.ts
bouncers slice + bouncerListNetworks / Add / Change / Del actions src/store/index.ts
Send helpers bouncerListNetworks / bouncerBind / bouncerAddNetwork / bouncerChangeNetwork / bouncerDelNetwork src/lib/irc/IRCClient.ts
CAP REQ for soju.im/bouncer-networks + -notify src/lib/irc/IRCClient.ts

What's deliberately NOT in this PR

  • No BOUNCER BIND lifecycle — comes in PR 2. We don't yet open one child connection per upstream network.
  • No UI — comes in PR 3. The store actions are fully wired so PR 3 is pure presentation.

Test plan

  • npm run format, npm run check, npm run test (812 pass / 1 skipped), npm run build clean
  • 27 new tests covering:
    • codec: round-trip, bare-key = empty value, attr deletion (attr= with empty value), repeated semicolons, unknown-escape pass-through
    • dispatcher: BOUNCER NETWORK / ADD / CHANGE / DEL, case-insensitive subcommand, * deletion marker, batch-tag carry-through
    • FAIL BOUNCER per-code decoding (INVALID_NETID, INVALID_ATTRIBUTE, NEED_ATTRIBUTE, unknown codes)
    • reducer: CAP ACK flags, merge of partial attr updates, attr deletion via empty value, network deletion, ISUPPORT BOUNCER_NETID, batch-end listed flip, lastError capture
  • Manual against soju isn't possible from this PR alone — once PR 2 lands, point it at a real soju and verify the LISTNETWORKS roundtrip + a live notify flow.

Summary by CodeRabbit

  • New Features

    • Full bouncer (multi-network) support: discover, list, add, edit, delete upstream networks; network panel, network form, and connect/open flows with per-network status and errors; store actions to manage networks.
  • Bug Fixes

    • More reliable capability negotiation and bind sequencing to ensure binds are applied before completion and avoid cross-server connection scoping issues.
    • CHATHISTORY handling adjusted for bouncer control sessions to avoid incorrect history requests.
  • Localization

    • Added/updated bouncer UI translations for many locales (some catalogs show encoding/garbled text needing review).
  • Tests

    • New tests covering attribute encoding, protocol handlers, and store behavior.

Adds the soju.im/bouncer-networks protocol layer plus a typed store
reducer. No UI surface yet -- that's PR C. No connection-binding logic
either -- that's PR B. This PR is intentionally headless so it can
land cleanly and be reviewed on its own merits.

What's in:

- src/lib/bouncerAttrs.ts -- codec for the message-tag-style attribute
  payload (key=value;key=value with \s \: \\ \r \n escapes). The
  encode and decode are symmetric and tolerate the corner cases in
  the spec (bare-key = empty value, attr=<empty> = deletion, repeated
  semicolons).
- src/lib/irc/handlers/bouncer.ts -- protocol dispatcher for the
  BOUNCER command (NETWORK / ADDNETWORK / CHANGENETWORK / DELNETWORK)
  and the FAIL BOUNCER standard-replies variant. Decodes the spec
  context shapes for INVALID_NETID, INVALID_ATTRIBUTE,
  READ_ONLY_ATTRIBUTE, UNKNOWN_ATTRIBUTE, NEED_ATTRIBUTE.
- IRCClient: BOUNCER_NETWORK, BOUNCER_{ADD,CHANGE,DEL}NETWORK_OK and
  BOUNCER_FAIL added to EventMap. bouncerListNetworks /
  bouncerBind / bouncerAddNetwork / bouncerChangeNetwork /
  bouncerDelNetwork added as send helpers.
- handlers/index.ts: BOUNCER routed to the new handler, FAIL routed
  to handleBouncerFail when parv[0] === BOUNCER (same per-command
  fan-out trick we use for FAIL METADATA).
- src/types/index.ts: BouncerNetwork + BouncerState types.
- src/store/handlers/bouncer.ts: reducer that upserts/merges networks,
  treats deleted=true and attr=<empty> per spec, picks up the
  soju.im/bouncer-networks and -notify caps on CAP ACK, watches
  BOUNCER_NETID ISUPPORT, and flips state.listed=true at the end of
  the soju.im/bouncer-networks BATCH.
- src/store/index.ts: bouncers: Record<serverId, BouncerState> in
  state. Public actions bouncerListNetworks / bouncerAddNetwork /
  bouncerChangeNetwork / bouncerDelNetwork that wrap the wire encode.
- CAP REQ list updated to ask for soju.im/bouncer-networks and
  soju.im/bouncer-networks-notify.

What's NOT in (deliberate; comes in PR B / C):

- BOUNCER BIND lifecycle: connecting to a bouncer and opening child
  connections per upstream network.
- UI: there is no surface yet. The store reducer covers add / change /
  delete / list / errors so PR C can be a pure UI layer.

Coverage:

- tests/lib/bouncerAttrs.test.ts (10) -- encode/decode round-trips,
  spec quirks (bare keys, repeated semicolons, unknown escapes).
- tests/protocol/bouncer.test.ts (9) -- BOUNCER NETWORK / ADD /
  CHANGE / DEL dispatch, batch-tag carry-through,
  case-insensitive subcommand match, FAIL BOUNCER variants for each
  spec error code.
- tests/store/bouncer.test.ts (8) -- merge / partial-update / attr
  deletion / network deletion / lastError / ISUPPORT BOUNCER_NETID /
  batch-end listed-flag.

Full suite green (812 / 1 skipped), build clean.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 14, 2026

PR changed again? Review this PR in Change Stack to compare snapshots and stay oriented.

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds Soju bouncer multi-network support: attribute codec, IRC BOUNCER and FAIL handlers, IRCClient bouncer commands with CAP-END bind sequencing, Zustand store handlers/actions, UI (panel + form), tests, and updated locale bundles.

Changes

Bouncer Multi-Network Feature

Layer / File(s) Summary
All changes
src/lib/bouncerAttrs.ts, src/lib/irc/handlers/bouncer.ts, src/lib/irc/IRCClient.ts, src/lib/irc/handlers/index.ts, src/lib/irc/handlers/connection.ts, src/lib/irc/IRCClientContext.ts, src/store/handlers/bouncer.ts, src/store/handlers/index.ts, src/store/index.ts, src/types/index.ts, src/components/ui/BouncerNetworkForm.tsx, src/components/ui/BouncerNetworksPanel.tsx, src/components/layout/ChatArea.tsx, src/components/layout/ServerList.tsx, src/components/ui/LinkSecurityWarningModal.tsx, tests/, src/locales/`
Implements bouncer attribute encode/decode, adds BOUNCER and FAIL BOUNCER protocol handlers, extends IRCClient with bouncer helpers and CAP-END bind sequencing, wires store state/actions and handlers, adds network form and panel UI with connect/edit/add/delete flows, includes tests for codec/handlers/store, and updates many locale bundles.

Estimated code review effort:
🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers:

  • matheusfillipe

Poem:

"A rabbit hops with code so bright,
Escaping semicolons through the night.
Bouncer nets in tidy rows,
CAPs and binds where traffic flows.
Tests green — the rabbit nibbles light."

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bouncer-networks/protocol

@github-actions
Copy link
Copy Markdown

Pages Preview
Preview URL: https://bouncer-networks-protocol.obsidianirc.pages.dev

Automated deployment preview for the PR in the Cloudflare Pages.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib/bouncerAttrs.ts`:
- Around line 39-42: encodeBouncerAttrs trusts attribute keys and can emit
malformed BOUNCER commands if keys contain forbidden chars; add a validator in
encodeBouncerAttrs that checks each key (in Object.entries) and rejects or
throws when it contains any of ;, =, whitespace, CR or LF (or otherwise invalid
characters), before performing escapeBouncerValue and joining, so only safe keys
are serialized; reference the encodeBouncerAttrs function and validate keys
prior to mapping/joining.

In `@src/store/handlers/bouncer.ts`:
- Around line 129-139: batchTypes is currently keyed only by batchId causing
collisions across servers; update the BATCH_START and BATCH_END handlers to
namespace keys by serverId (e.g. use `${serverId}:${batchId}` or a nested Map
keyed first by serverId then batchId) so lookups/deletes match the same
namespacing. Ensure the BATCH_START handler captures serverId from its event
payload (it currently only destructures { batchId, type }) and stores the
namespaced key when type === "soju.im/bouncer-networks", and update the
BATCH_END handler to check and delete using the same namespaced key before
calling store.setState/ensureBouncer.

In `@src/store/index.ts`:
- Around line 3782-3790: The bouncerAddNetwork and bouncerChangeNetwork handlers
currently pass all attributes (including server-owned read-only fields like
"state" and "error") to ircClient, causing READ_ONLY_ATTRIBUTE errors; update
these functions (bouncerAddNetwork and bouncerChangeNetwork) to filter out
read-only keys before calling encodeBouncerAttrs/ircClient (e.g., remove
"state", "error" and any other server-owned fields from attrs) so only writable
attributes are sent. Ensure the filtering happens immediately before
encodeBouncerAttrs is called so callers can keep full objects but writes send
only allowed fields.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 324f47e2-e9d1-4b10-8b5d-ba4905b77150

📥 Commits

Reviewing files that changed from the base of the PR and between e153b25 and 90a4615.

📒 Files selected for processing (11)
  • src/lib/bouncerAttrs.ts
  • src/lib/irc/IRCClient.ts
  • src/lib/irc/handlers/bouncer.ts
  • src/lib/irc/handlers/index.ts
  • src/store/handlers/bouncer.ts
  • src/store/handlers/index.ts
  • src/store/index.ts
  • src/types/index.ts
  • tests/lib/bouncerAttrs.test.ts
  • tests/protocol/bouncer.test.ts
  • tests/store/bouncer.test.ts

Comment thread src/lib/bouncerAttrs.ts
Comment on lines +39 to +42
export function encodeBouncerAttrs(attrs: Record<string, string>): string {
return Object.entries(attrs)
.map(([k, v]) => (v === "" ? k : `${k}=${escapeBouncerValue(v)}`))
.join(";");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate attribute keys before wire serialization.

encodeBouncerAttrs currently trusts keys verbatim. Keys containing ;, =, whitespace, or CR/LF can break token structure and produce malformed outbound BOUNCER commands. Add a strict key validator before join/encode.

Suggested fix
+const BOUNCER_ATTR_KEY_RE = /^[A-Za-z0-9._/-]+$/;
+
 export function encodeBouncerAttrs(attrs: Record<string, string>): string {
   return Object.entries(attrs)
+    .map(([k, v]) => {
+      if (!BOUNCER_ATTR_KEY_RE.test(k)) {
+        throw new Error(`Invalid bouncer attribute key: ${k}`);
+      }
+      return [k, v] as const;
+    })
     .map(([k, v]) => (v === "" ? k : `${k}=${escapeBouncerValue(v)}`))
     .join(";");
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/bouncerAttrs.ts` around lines 39 - 42, encodeBouncerAttrs trusts
attribute keys and can emit malformed BOUNCER commands if keys contain forbidden
chars; add a validator in encodeBouncerAttrs that checks each key (in
Object.entries) and rejects or throws when it contains any of ;, =, whitespace,
CR or LF (or otherwise invalid characters), before performing escapeBouncerValue
and joining, so only safe keys are serialized; reference the encodeBouncerAttrs
function and validate keys prior to mapping/joining.

Comment on lines +129 to +139
const batchTypes = new Map<string, string>(); // batchId -> type
ircClient.on("BATCH_START", ({ batchId, type }) => {
if (type === "soju.im/bouncer-networks") batchTypes.set(batchId, type);
});
ircClient.on("BATCH_END", ({ serverId, batchId }) => {
if (batchTypes.get(batchId) !== "soju.im/bouncer-networks") return;
batchTypes.delete(batchId);
store.setState((state) => ({
bouncers: ensureBouncer(state, serverId, { listed: true }),
}));
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Batch ID collision across servers.

The batchTypes Map is keyed only by batchId, but batch IDs are only unique per connection, not globally. If multiple servers (bouncer + upstreams) use the same batch ID concurrently, the Map entries will collide, causing the listed flag to be set incorrectly or not at all.

🔒 Proposed fix to namespace batch IDs by serverId
-  const batchTypes = new Map<string, string>(); // batchId -> type
+  const batchTypes = new Map<string, string>(); // "serverId:batchId" -> type
   ircClient.on("BATCH_START", ({ serverId, batchId, type }) => {
-    if (type === "soju.im/bouncer-networks") batchTypes.set(batchId, type);
+    if (type === "soju.im/bouncer-networks")
+      batchTypes.set(`${serverId}:${batchId}`, type);
   });
   ircClient.on("BATCH_END", ({ serverId, batchId }) => {
-    if (batchTypes.get(batchId) !== "soju.im/bouncer-networks") return;
-    batchTypes.delete(batchId);
+    const key = `${serverId}:${batchId}`;
+    if (batchTypes.get(key) !== "soju.im/bouncer-networks") return;
+    batchTypes.delete(key);
     store.setState((state) => ({
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const batchTypes = new Map<string, string>(); // batchId -> type
ircClient.on("BATCH_START", ({ batchId, type }) => {
if (type === "soju.im/bouncer-networks") batchTypes.set(batchId, type);
});
ircClient.on("BATCH_END", ({ serverId, batchId }) => {
if (batchTypes.get(batchId) !== "soju.im/bouncer-networks") return;
batchTypes.delete(batchId);
store.setState((state) => ({
bouncers: ensureBouncer(state, serverId, { listed: true }),
}));
});
const batchTypes = new Map<string, string>(); // "serverId:batchId" -> type
ircClient.on("BATCH_START", ({ serverId, batchId, type }) => {
if (type === "soju.im/bouncer-networks")
batchTypes.set(`${serverId}:${batchId}`, type);
});
ircClient.on("BATCH_END", ({ serverId, batchId }) => {
const key = `${serverId}:${batchId}`;
if (batchTypes.get(key) !== "soju.im/bouncer-networks") return;
batchTypes.delete(key);
store.setState((state) => ({
bouncers: ensureBouncer(state, serverId, { listed: true }),
}));
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/store/handlers/bouncer.ts` around lines 129 - 139, batchTypes is
currently keyed only by batchId causing collisions across servers; update the
BATCH_START and BATCH_END handlers to namespace keys by serverId (e.g. use
`${serverId}:${batchId}` or a nested Map keyed first by serverId then batchId)
so lookups/deletes match the same namespacing. Ensure the BATCH_START handler
captures serverId from its event payload (it currently only destructures {
batchId, type }) and stores the namespaced key when type ===
"soju.im/bouncer-networks", and update the BATCH_END handler to check and delete
using the same namespaced key before calling store.setState/ensureBouncer.

Comment thread src/store/index.ts
Comment on lines +3782 to +3790
bouncerAddNetwork: (bouncerServerId, attrs) => {
ircClient.bouncerAddNetwork(bouncerServerId, encodeBouncerAttrs(attrs));
},
bouncerChangeNetwork: (bouncerServerId, netid, attrs) => {
ircClient.bouncerChangeNetwork(
bouncerServerId,
netid,
encodeBouncerAttrs(attrs),
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Strip read-only bouncer fields before ADD/CHANGE sends.

Line 3783 and Line 3789 encode and forward all attrs, including server-owned fields like state/error. If callers reuse stored network attrs, these writes will fail with READ_ONLY_ATTRIBUTE.

Suggested fix
-import { encodeBouncerAttrs } from "../lib/bouncerAttrs";
+import {
+  BOUNCER_READ_ONLY_ATTRIBUTES,
+  encodeBouncerAttrs,
+} from "../lib/bouncerAttrs";
+
+const toWritableBouncerAttrs = (attrs: Record<string, string>) =>
+  Object.fromEntries(
+    Object.entries(attrs).filter(
+      ([k]) => !BOUNCER_READ_ONLY_ATTRIBUTES.has(k),
+    ),
+  );

   bouncerAddNetwork: (bouncerServerId, attrs) => {
-    ircClient.bouncerAddNetwork(bouncerServerId, encodeBouncerAttrs(attrs));
+    ircClient.bouncerAddNetwork(
+      bouncerServerId,
+      encodeBouncerAttrs(toWritableBouncerAttrs(attrs)),
+    );
   },
   bouncerChangeNetwork: (bouncerServerId, netid, attrs) => {
     ircClient.bouncerChangeNetwork(
       bouncerServerId,
       netid,
-      encodeBouncerAttrs(attrs),
+      encodeBouncerAttrs(toWritableBouncerAttrs(attrs)),
     );
   },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/store/index.ts` around lines 3782 - 3790, The bouncerAddNetwork and
bouncerChangeNetwork handlers currently pass all attributes (including
server-owned read-only fields like "state" and "error") to ircClient, causing
READ_ONLY_ATTRIBUTE errors; update these functions (bouncerAddNetwork and
bouncerChangeNetwork) to filter out read-only keys before calling
encodeBouncerAttrs/ircClient (e.g., remove "state", "error" and any other
server-owned fields from attrs) so only writable attributes are sent. Ensure the
filtering happens immediately before encodeBouncerAttrs is called so callers can
keep full objects but writes send only allowed fields.

matheusfillipe
matheusfillipe previously approved these changes May 17, 2026
* feat(bouncer-networks): BIND lifecycle + child connections (PR B of 3)

Stacks on PR #214 (protocol + store). Wires the connection lifecycle
for soju.im/bouncer-networks: one bouncer endpoint can now host N
child connections, each bound to a different upstream network via
BOUNCER BIND before CAP END. Still headless -- UI lands in PR C.

What's in:

- IRCClient.sendCapEnd(serverId): centralised CAP END sender that
  emits a queued 'BOUNCER BIND <netid>' first when the serverId was
  flagged as a bouncer child via setPendingBouncerBind(). All six
  CAP END call sites (timeout, no-caps-to-request, post-CAP-ACK,
  CAP NAK, SASL success/fail, LinkSecurityWarning resume, post-
  account-registration) now flow through this one helper.
- IRCClient.pendingConnections dedup keyed by serverId when one is
  given, falling back to host:port. Without this, multiple child
  connections sharing the bouncer endpoint would collapse onto a
  single Promise<Server>.
- Server / ServerConfig: bouncerServerId, bouncerNetid,
  isBouncerControl fields plumbed through the type, persistence,
  and the initial Server seed in connectToSavedServers.
- store.bouncerConnectNetwork(parentServerId, netid): the public
  API. Resolves the parent's credentials, derives a deterministic
  childId via uuidv5(parent:netid, CHANNEL_NAMESPACE), saves the
  child ServerConfig, seeds the child Server row in 'connecting'
  state, calls setPendingBouncerBind, and dispatches the IRCClient
  connect with the explicit childId.
- connectToSavedServers: detects bouncer children at restore time
  and bypasses the store-level connect() wrapper (which would
  resolve them onto the parent's id via host:port lookup), going
  direct to ircClient.connect() with the explicit child id and a
  setPendingBouncerBind call beforehand.

What's NOT in (deliberate; PR C):

- UI for managing bouncer networks (list / add / change / delete).
- Server-list rendering that groups child connections under their
  parent.
- Modal for picking which networks to connect to.

Coverage:

- tests/protocol/bouncerBind.test.ts -- sendCapEnd emits CAP END
  alone when no BIND queued, BOUNCER BIND <netid> immediately
  before CAP END when queued, BIND is consumed (single-use), BINDs
  are scoped per serverId.
- tests/store/bouncerConnect.test.ts -- queues BIND on a fresh
  childId before calling connect, seeds the child Server with
  parent linkage + 'connecting' state + friendly name from the
  BOUNCER NETWORK attributes, persists the child ServerConfig with
  the parent's SASL credentials, repeated calls are idempotent for
  persistence, returns undefined when the parent has no saved
  config.

Full suite green (821 / 1 skipped), build clean.

* feat(bouncer-networks): management UI (PR C of 3) (#216)

* feat(bouncer-networks): management UI (PR C of 3)

Adds the user-facing UI for the soju.im/bouncer-networks extension:

- BouncerNetworksModal: list/add/edit modes with state badges
  (green/yellow-pulsing/gray dots), connected-first sort,
  inline-confirm delete, success highlight on ack, empty state
- BouncerNetworkForm: TLS-on-default toggle, only-changed-attrs
  diff for edits, field-level error surfacing tied to FAIL BOUNCER,
  read-only attribute filtering
- ServerList: "Manage Networks" affordance on bouncer-control
  servers (only shown when supported && not a child binding)
- store: bouncerNetworksModalServerId UI slot + toggle action
- App: globally mounted modal driven by store state

Includes tests for form helpers (attrsToValues/valuesToAttrs
diff logic) and modal interactions, plus translations across
all 18 supported locales.

* fix(bouncer-networks): match CAP_ACKNOWLEDGED per-cap event shape

The auth-side CAP ACK splitter re-emits CAP_ACKNOWLEDGED once per cap
with the cap name in `key` (and any =value in `capabilities`). My
handler was treating the event as a bulk "ACK"/"NEW" tag with a
space-separated cap list, so `key !== "ACK"` always returned early
and `supported` never flipped to true — the Manage Networks button
never appeared even when soju acked the cap.

Fix: just check `key` against the two cap names directly. Update
test to fire one event per cap to match the real emission pattern.

* feat(bouncer-networks): auto-bind each upstream as a child server

Previously you could add a network in the management modal and watch
it sit there as a row, but there was no way to actually use the
connection -- the BIND lifecycle from PR B was wired up but never
dispatched.

Now: on every BOUNCER_NETWORK upsert from a control session, fire
bouncerConnectNetwork. The action is made idempotent (early-return
if a child server already exists for the bouncer+netid pair) so
this is safe to call on every snapshot, every notify update, and
on reconnects.

Result: networks added in the modal show up as their own server
bubbles in the sidebar, ready to /join channels on the upstream.

* fix(bouncer-networks): return undefined from idempotent bouncerConnectNetwork

The early-return for the already-bound case was returning childId (a
string) but the action's declared signature is Promise<Server | undefined>,
breaking tsc. Return undefined -- no caller inspects the value.

* feat(bouncer-networks): inline network browser in chat area + connect URL fix

Replaces the floating layer-group icon + modal with a proper chat-area
panel: select the bouncer in the sidebar and the main pane becomes a
list of upstream networks with explicit Connect/Open buttons per row.
Auto-connect-everything is gone -- the user picks which networks to
bind to.

Also fixes the actual binding bug: child connections were inheriting
parent.host (the bare hostname after URL parsing) instead of the saved
WSS URL with its /socket path, so wss://host:6662/socket children were
hitting wss://host:6662/ and getting refused by soju. Use savedParent
to preserve the original URL form.

Drops:
- BouncerNetworksModal + its test (replaced by BouncerNetworksPanel)
- bouncerNetworksModalServerId UI slot + toggleBouncerNetworksModal action
- FaLayerGroup overlay button on ServerIcon

Adds:
- BouncerNetworksPanel rendered when selected server is bouncer-control
  and no channel is selected
- "Connect" button per row -> dispatches bouncerConnectNetwork +
  switches the sidebar selection to the new child server
- "Open" button when the child is already present (just selects it)

* fix(bouncer-networks): gate child reconnects on parent ready event

connectToSavedServers used to fire parent + child reconnects in parallel
at app startup.  Both opened WSS to the same soju endpoint and raced
their SASL/BIND handshakes against each other, surfacing as 'invalid
password' against the child sessions even though the credentials were
identical and the manual click path worked fine.

Split the reconnect into two phases: parents (and standalone servers)
go first, each child Server row is seeded into state immediately so the
UI doesn't blink, then a one-shot `ready` listener dispatches every
child of a parent as soon as that parent finishes registration.  If a
parent fails outright the listener detaches and its children are marked
disconnected so the UI stops spinning.

Tests pin: seeded child row, deferred dispatch, ready-fire dispatch,
and that unrelated ready events are ignored.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🟡 Minor comments (18)
src/components/ui/LinkSecurityWarningModal.tsx-124-126 (1)

124-126: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Mark CAP negotiation complete before resuming the connection.

This path now sends CAP END and USER, but it never flips ircClient.capNegotiationComplete to true. Any code that checks isCapNegotiationComplete(serverId) will keep treating this session as mid-negotiation after the user accepts the warning. Set the flag here or route this through one helper that completes the whole CAP flow.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/ui/LinkSecurityWarningModal.tsx` around lines 124 - 126, The
CAP flow resumes by calling ircClient.sendCapEnd(serverId) and
ircClient.userOnConnect(serverId) but never flips the negotiation flag; update
this path to mark the CAP negotiation complete by setting
ircClient.capNegotiationComplete[serverId] = true (or call the existing helper
that finalizes CAP negotiation) before or immediately after sendCapEnd so that
isCapNegotiationComplete(serverId) returns true and subsequent logic treats the
session as post-negotiation.
src/locales/fr/messages.po-62-64 (1)

62-64: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

French catalog still contains untranslated new UI strings.

These msgstr values are empty, so the new bouncer UI falls back to English in fr.

Suggested translations
 msgid "{0} network{1} — pick one to join"
-msgstr ""
+msgstr "{0} réseau{1} — choisissez-en un pour rejoindre"

 msgid "Back to network list"
-msgstr ""
+msgstr "Retour à la liste des réseaux"

 msgid "Open"
-msgstr ""
+msgstr "Ouvrir"

Also applies to: 387-390, 1770-1773

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/locales/fr/messages.po` around lines 62 - 64, Update the French .po
entries where msgstr is empty for the new UI strings (e.g. the msgid "{0}
network{1} — pick one to join" and the other occurrences noted around lines
387-390 and 1770-1773) by supplying proper French translations and preserving
interpolation placeholders ({0}, {1}) and punctuation/emdash exactly as in the
msgid; ensure each corresponding msgstr is filled in the same file (messages.po)
so the fr locale no longer falls back to English.
src/components/ui/BouncerNetworkForm.tsx-166-171 (1)

166-171: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Localize the host placeholder through Lingui.

placeholder="irc.libera.chat" is user-visible and won’t be extracted for translations.

Suggested fix
-            placeholder="irc.libera.chat"
+            placeholder={t`irc.libera.chat`}

As per coding guidelines "Wrap all user-visible text with LinguiJS <Trans> for JSX children, t for JSX props and simple strings, and t with interpolation for variables".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/ui/BouncerNetworkForm.tsx` around lines 166 - 171, The
placeholder string for the TextInput in BouncerNetworkForm.tsx is hard-coded and
not localizable; import the Lingui helper (t) from '`@lingui/macro`' at the top of
the file and replace placeholder="irc.libera.chat" with a Lingui-wrapped prop
(use t for JSX props) so the host placeholder becomes localizable for
translations for the TextInput component in BouncerNetworkForm.
src/components/ui/BouncerNetworksPanel.tsx-27-34 (1)

27-34: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Localize connection-state labels instead of hardcoding English text.

STATE_COPY embeds user-visible labels at module scope, so they are never extracted to locale catalogs.

Suggested direction
-const STATE_COPY: Record<
-  string,
-  { label: string; dotClass: string; pulse?: boolean }
-> = {
-  connected: { label: "Connected", dotClass: "bg-green-400" },
-  connecting: { label: "Connecting…", dotClass: "bg-yellow-400", pulse: true },
-  disconnected: { label: "Disconnected", dotClass: "bg-discord-text-muted" },
-};
+// inside component body
+const stateCopy = useMemo(
+  () => ({
+    connected: { label: t`Connected`, dotClass: "bg-green-400" },
+    connecting: {
+      label: t`Connecting…`,
+      dotClass: "bg-yellow-400",
+      pulse: true,
+    },
+    disconnected: { label: t`Disconnected`, dotClass: "bg-discord-text-muted" },
+  }),
+  [t],
+);

As per coding guidelines "Wrap all user-visible text with LinguiJS <Trans> for JSX children, t for JSX props and simple strings, and t with interpolation for variables" and "Do not use t macro at module scope; move translatable strings inside function bodies to ensure they evaluate after i18n.activate() runs".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/ui/BouncerNetworksPanel.tsx` around lines 27 - 34, STATE_COPY
currently embeds English labels at module scope which prevents extraction and
breaks Lingui's runtime activation; refactor by removing user-visible strings
from STATE_COPY (keep keys like connected/connecting/disconnected and
dotClass/pulse only) and move translation into the component render path (inside
the BouncerNetworksPanel function) using Lingui's t for prop strings or <Trans>
for JSX children so translations evaluate after i18n.activate(); update any
usages of STATE_COPY.label to call the translator (e.g., t`...` or <Trans> ...)
at render time.
src/locales/it/messages.po-63-63 (1)

63-63: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fill missing Italian translations for new bouncer UI strings.

These msgstr values are empty, so Italian users will see fallback source text in key navigation/actions for the new bouncer flow.

Also applies to: 389-389, 1772-1772

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/locales/it/messages.po` at line 63, Fill the empty Italian translations
in the messages.po entries for the new bouncer UI by replacing the empty msgstr
"" values with accurate Italian translations that match the corresponding msgid
source strings; update each affected msgstr (the entries around the currently
empty msgstr at the reported locations) ensuring proper escaping and plural
forms if present, keep the meaning and UI tone consistent with other Italian
locale strings, and then run the PO linter/validation (or msgfmt) to verify
there are no syntax or encoding errors.
src/locales/ja/messages.po-63-63 (1)

63-63: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add missing Japanese translations for newly introduced bouncer labels/actions.

These entries are currently untranslated (msgstr ""), which causes fallback-language strings in Japanese UI flows.

Also applies to: 389-389, 1772-1772

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/locales/ja/messages.po` at line 63, Replace the empty msgstr entries for
the new bouncer label/action msgid entries in the ja messages.po with proper
Japanese translations (fill msgstr for each corresponding msgid), handle any
plural forms by populating msgid_plural and msgstr[N] as appropriate, ensure the
file remains UTF-8 encoded and escapes/quoting match existing PO format, and run
your PO linter/msgfmt to validate the updated src/locales/ja/messages.po before
committing.
src/locales/ko/messages.po-1770-1772 (1)

1770-1772: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Complete the missing translation for action button.

The msgstr is empty, so Korean users will see English text instead. This breaks localization consistency for action buttons in the bouncer networks panel.

Suggested translation:

 msgid "Open"
-msgstr ""
+msgstr "열기"

Note: "열기" is the standard Korean translation for "Open" as an action verb, consistent with similar UI action verbs throughout the file.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/locales/ko/messages.po` around lines 1770 - 1772, The translation entry
for msgid "Open" in the Korean PO file is empty; update the msgstr for that
msgid to the Korean action verb "열기" so the BouncerNetworksPanel action button
is localized consistently (locate the msgid "Open" entry in the messages.po file
and set msgstr "열기").
src/locales/ko/messages.po-59-63 (1)

59-63: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Complete the missing translation for network selection prompt.

The msgstr is empty, so Korean users will see English text instead. This breaks localization completeness for the bouncer networks panel.

Suggested translation:

 msgid "{0} network{1} — pick one to join"
-msgstr ""
+msgstr "{0}개의 네트워크 — 참여할 네트워크를 선택하세요"

Note: Korean doesn't require plural forms like English "s", so {1} can be omitted in the translation while keeping {0} for the count.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/locales/ko/messages.po` around lines 59 - 63, Add a Korean translation
for the msgid "{0} network{1} — pick one to join" in src/locales/ko/messages.po
(used by BouncerNetworksPanel.tsx): set msgstr to a Korean string that preserves
the {0} placeholder (count) and omits {1} since Korean doesn't use English
plural "s" (for example, "네트워크 {0}개 — 참여할 네트워크를 선택하세요" or similar), ensuring the
placeholder syntax remains unchanged.
src/locales/ko/messages.po-387-389 (1)

387-389: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Complete the missing translation for navigation action.

The msgstr is empty, so Korean users will see English text instead. This is inconsistent with other translated navigation strings in the file (e.g., line 127: "← 뒤로").

Suggested translation:

 msgid "Back to network list"
-msgstr ""
+msgstr "네트워크 목록으로 돌아가기"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/locales/ko/messages.po` around lines 387 - 389, Add the missing Korean
translation for the msgid "Back to network list" in src/locales/ko/messages.po
by filling in the msgstr for that entry (the entry referencing
src/components/ui/BouncerNetworksPanel.tsx with msgid "Back to network list");
use the suggested translation (e.g., "← 뒤로 네트워크 목록으로") or another accurate
Korean phrase and ensure the msgstr is not empty so the UI shows the translated
label.
src/locales/cs/messages.po-63-64 (1)

63-64: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Complete missing Czech translations for new bouncer labels.

These new entries have empty msgstr, so users in Czech locale will see fallback English in the bouncer flow. Please fill these translations and regenerate locale artifacts.

Also applies to: 389-390, 1772-1773

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/locales/cs/messages.po` around lines 63 - 64, Fill the empty Czech
translations in the Czech PO file for the new bouncer labels by replacing each
blank msgstr for the bouncer-related msgid entries with correct Czech text (the
affected entries are the new bouncer label msgids currently showing empty
msgstr). After updating the msgstr values for those entries, run the project's
locale regeneration/compile step to rebuild the locale artifacts so the changes
take effect. Ensure you update all occurrences (the three blank msgstr groups
referenced) and keep msgid text unchanged.
src/locales/de/messages.po-63-64 (1)

63-64: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fill missing German translations before merge.

These entries are still untranslated (msgstr ""), which leaves English text in the German bouncer UI. Please provide German msgstr values and regenerate message bundles.

Also applies to: 389-390, 1772-1773

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/locales/de/messages.po` around lines 63 - 64, Replace the empty msgstr
entries in the German .po file (messages.po) with proper German translations
matching each msgid (including plural forms where applicable) for the
untranslated entries flagged in the review (three occurrences), then run the
project's i18n message bundle generation step to regenerate the compiled locale
bundles so the UI uses the new German strings.
src/locales/es/messages.po-59-64 (1)

59-64: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fill missing Spanish translations for newly added bouncer UI strings.

msgstr is empty for three user-facing entries, so Spanish users will see English fallback text in the new flow.

💬 Suggested translations
 msgid "{0} network{1} — pick one to join"
-msgstr ""
+msgstr "{0} red{1} — elige una para unirte"

 msgid "Back to network list"
-msgstr ""
+msgstr "Volver a la lista de redes"

 msgid "Open"
-msgstr ""
+msgstr "Abrir"

Also applies to: 387-390, 1770-1773

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/locales/es/messages.po` around lines 59 - 64, Populate the empty msgstr
for the new bouncer UI strings in src/locales/es/messages.po by providing a
Spanish translation that preserves the placeholders {0} and {1} used for
pluralization; for the msgid "{0} network{1} — pick one to join" (referenced
from src/components/ui/BouncerNetworksPanel.tsx) set msgstr to a Spanish
equivalent such as "{0} red{1} — elige una para unirte", and apply the same
approach to the other identical entries mentioned (around the other ranges) so
all occurrences use the translated string with placeholders intact.
src/locales/fi/messages.po-62-63 (1)

62-63: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Complete the missing Finnish translations.

Three user-facing strings in the bouncer networks UI lack Finnish translations and will display in English:

  • Line 62-63: "{0} network{1} — pick one to join"
  • Line 388-389: "Back to network list"
  • Line 771-772: "Open"

Finnish users will see mixed-language UI text. Complete these translations to maintain a consistent localized experience.

Also applies to: 388-389, 771-772

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/locales/fi/messages.po` around lines 62 - 63, The PO file contains three
untranslated msgid entries — "{0} network{1} — pick one to join", "Back to
network list", and "Open" — causing mixed English UI for Finnish users; add
appropriate Finnish msgstr values for each msgid in the same file (provide
correct Finnish translations preserving placeholders like {0} and {1} exactly),
verify encoding and plural/formatting is preserved, and run a quick lint/compile
of localization assets to ensure the new translations are picked up by functions
that render the bouncer networks UI.
src/locales/fi/messages.mjs-1-1 (1)

1-1: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Missing Finnish translation for new bouncer network string.

The message ID STmlpb ("Back to network list") remains in English instead of being translated to Finnish. This will cause the back button's aria-label in the bouncer networks panel to display in English for Finnish-speaking users.

As per the PR objectives, this layer adds bouncer network management UI strings to locale catalogs. The translation should be added to the source .po file and regenerated.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/locales/fi/messages.mjs` at line 1, The locale file is missing a Finnish
translation for message ID "STmlpb" (currently "Back to network list"); add the
Finnish string (e.g. "Takaisin verkkojen luetteloon" or your approved
translation) to the Finnish .po source entry for STmlpb and then regenerate the
compiled messages (which produce src/locales/fi/messages.mjs) so that
messages.STmlpb contains the Finnish text instead of English.
src/locales/ja/messages.mjs-1-1 (1)

1-1: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Missing Japanese translation for new bouncer network string.

The message ID STmlpb ("Back to network list") remains in English instead of being translated to Japanese. This will cause the back button's aria-label in the bouncer networks panel to display in English for Japanese-speaking users.

As per the PR objectives, this layer adds bouncer network management UI strings to locale catalogs. The translation should be added to the source .po file and regenerated.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/locales/ja/messages.mjs` at line 1, The locale entry for message ID
STmlpb is still English ("Back to network list"); add its Japanese translation
(e.g. "ネットワークリストに戻る") to the source .po for Japanese and then regenerate the
compiled locale bundle so messages.mjs includes the new translated value for
STmlpb; ensure the updated messages.mjs contains STmlpb mapped to the Japanese
string used by the bouncer networks panel.
src/locales/fr/messages.mjs-1-1 (1)

1-1: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Missing French translation for new bouncer network string.

The message ID STmlpb ("Back to network list") remains in English instead of being translated to French. This will cause the back button's aria-label in the bouncer networks panel to display in English for French-speaking users.

As per the PR objectives, this layer adds bouncer network management UI strings to locale catalogs. The translation should be added to the source .po file and regenerated.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/locales/fr/messages.mjs` at line 1, The messages catalog contains an
untranslated entry "STmlpb" ("Back to network list"); update the French
translation for message ID STmlpb in the source .po locale (and then regenerate
the messages.mjs so messages includes the French string) — e.g. set STmlpb to
"Retour à la liste des réseaux" in the FR .po and run the locale build that
produces the messages const in messages.mjs.
src/locales/es/messages.mjs-1-1 (1)

1-1: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Missing Spanish translation for new bouncer network string.

The message ID STmlpb ("Back to network list") remains in English instead of being translated to Spanish. This will cause the back button's aria-label in the bouncer networks panel to display in English for Spanish-speaking users.

As per the PR objectives, this layer adds bouncer network management UI strings to locale catalogs. The translation should be added to the source .po file and regenerated.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/locales/es/messages.mjs` at line 1, The locale file is missing a Spanish
translation for the new bouncer network string id "STmlpb" (currently "Back to
network list"); add the Spanish translation (e.g., "Volver a la lista de redes")
for message id "STmlpb" in the Spanish source .po file, then regenerate the
locale bundle so src/locales/es/messages.mjs (messages JSON) includes the
updated entry for "STmlpb".
src/locales/ko/messages.mjs-1-1 (1)

1-1: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Missing Korean translation for new bouncer network string.

The message ID STmlpb ("Back to network list") remains in English instead of being translated to Korean. This will cause the back button's aria-label in the bouncer networks panel to display in English for Korean-speaking users.

As per the PR objectives, this layer adds bouncer network management UI strings to locale catalogs. The translation should be added to the source .po file and regenerated.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/locales/ko/messages.mjs` at line 1, The locale bundle still contains the
English string for message ID STmlpb ("Back to network list"); add the Korean
translation for STmlpb in the Korean .po source (the entry for msgid "Back to
network list") and then re-run the localization build/regeneration step so
src/locales/ko/messages.mjs is regenerated with the new Korean string used by
the bouncer networks panel aria-label.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/ui/BouncerNetworksPanel.tsx`:
- Around line 90-103: The optimistic 500ms timeout in the useEffect that
references bouncer, pendingFor, bouncer.lastError, setConfirmedSuccessFor,
setMode, and setPendingFor can hide real failures; replace the wall-clock timer
with a protocol/store-correlated completion signal: stop using setTimeout and
instead wait for an explicit ACK or observable state change tied to the
operation (e.g., a bouncer.ackFor/pendingAck field, a transition on
bouncer.state, or a network-list mutation that includes the new network) and
only call
setConfirmedSuccessFor(pendingFor)/setMode({kind:"list"})/setPendingFor(null)
when that success signal is observed (or immediately handle bouncer.lastError
when a FAIL arrives); ensure you still clear any temporary
listeners/subscriptions in the effect cleanup.

In `@src/lib/irc/handlers/connection.ts`:
- Around line 175-177: The CAP NAK branch currently ends CAP negotiation but
doesn't finish registration; after the existing calls to
ctx.sendCapEnd(serverId) and ctx.capNegotiationComplete.set(serverId, true) in
the subcommand === "NAK" branch, invoke ctx.userOnConnect(serverId) so the
client proceeds with the USER/registration flow (mirror the behavior in the
"ACK", timeout, and SASL completion paths by calling ctx.userOnConnect with the
same serverId).

In `@src/lib/irc/IRCClient.ts`:
- Around line 649-653: The pending-connection key is derived as serverId ??
`${host}:${port}` in connect(), but disconnect()/removeServer() still delete
`${server.host}:${server.port}` causing stale promises; centralize key
derivation into a single helper (e.g., deriveConnectionKey(server, host, port,
serverId)) and replace all places that set/delete pendingConnections (connect(),
disconnect(), removeServer(), and socket.onclose handler) to call that helper so
the same connectionKey is used for both set and delete; ensure the helper is
used where connectionKey is currently declared and where
`${server.host}:${server.port}` is constructed.

---

Minor comments:
In `@src/components/ui/BouncerNetworkForm.tsx`:
- Around line 166-171: The placeholder string for the TextInput in
BouncerNetworkForm.tsx is hard-coded and not localizable; import the Lingui
helper (t) from '`@lingui/macro`' at the top of the file and replace
placeholder="irc.libera.chat" with a Lingui-wrapped prop (use t for JSX props)
so the host placeholder becomes localizable for translations for the TextInput
component in BouncerNetworkForm.

In `@src/components/ui/BouncerNetworksPanel.tsx`:
- Around line 27-34: STATE_COPY currently embeds English labels at module scope
which prevents extraction and breaks Lingui's runtime activation; refactor by
removing user-visible strings from STATE_COPY (keep keys like
connected/connecting/disconnected and dotClass/pulse only) and move translation
into the component render path (inside the BouncerNetworksPanel function) using
Lingui's t for prop strings or <Trans> for JSX children so translations evaluate
after i18n.activate(); update any usages of STATE_COPY.label to call the
translator (e.g., t`...` or <Trans> ...) at render time.

In `@src/components/ui/LinkSecurityWarningModal.tsx`:
- Around line 124-126: The CAP flow resumes by calling
ircClient.sendCapEnd(serverId) and ircClient.userOnConnect(serverId) but never
flips the negotiation flag; update this path to mark the CAP negotiation
complete by setting ircClient.capNegotiationComplete[serverId] = true (or call
the existing helper that finalizes CAP negotiation) before or immediately after
sendCapEnd so that isCapNegotiationComplete(serverId) returns true and
subsequent logic treats the session as post-negotiation.

In `@src/locales/cs/messages.po`:
- Around line 63-64: Fill the empty Czech translations in the Czech PO file for
the new bouncer labels by replacing each blank msgstr for the bouncer-related
msgid entries with correct Czech text (the affected entries are the new bouncer
label msgids currently showing empty msgstr). After updating the msgstr values
for those entries, run the project's locale regeneration/compile step to rebuild
the locale artifacts so the changes take effect. Ensure you update all
occurrences (the three blank msgstr groups referenced) and keep msgid text
unchanged.

In `@src/locales/de/messages.po`:
- Around line 63-64: Replace the empty msgstr entries in the German .po file
(messages.po) with proper German translations matching each msgid (including
plural forms where applicable) for the untranslated entries flagged in the
review (three occurrences), then run the project's i18n message bundle
generation step to regenerate the compiled locale bundles so the UI uses the new
German strings.

In `@src/locales/es/messages.mjs`:
- Line 1: The locale file is missing a Spanish translation for the new bouncer
network string id "STmlpb" (currently "Back to network list"); add the Spanish
translation (e.g., "Volver a la lista de redes") for message id "STmlpb" in the
Spanish source .po file, then regenerate the locale bundle so
src/locales/es/messages.mjs (messages JSON) includes the updated entry for
"STmlpb".

In `@src/locales/es/messages.po`:
- Around line 59-64: Populate the empty msgstr for the new bouncer UI strings in
src/locales/es/messages.po by providing a Spanish translation that preserves the
placeholders {0} and {1} used for pluralization; for the msgid "{0} network{1} —
pick one to join" (referenced from src/components/ui/BouncerNetworksPanel.tsx)
set msgstr to a Spanish equivalent such as "{0} red{1} — elige una para unirte",
and apply the same approach to the other identical entries mentioned (around the
other ranges) so all occurrences use the translated string with placeholders
intact.

In `@src/locales/fi/messages.mjs`:
- Line 1: The locale file is missing a Finnish translation for message ID
"STmlpb" (currently "Back to network list"); add the Finnish string (e.g.
"Takaisin verkkojen luetteloon" or your approved translation) to the Finnish .po
source entry for STmlpb and then regenerate the compiled messages (which produce
src/locales/fi/messages.mjs) so that messages.STmlpb contains the Finnish text
instead of English.

In `@src/locales/fi/messages.po`:
- Around line 62-63: The PO file contains three untranslated msgid entries —
"{0} network{1} — pick one to join", "Back to network list", and "Open" —
causing mixed English UI for Finnish users; add appropriate Finnish msgstr
values for each msgid in the same file (provide correct Finnish translations
preserving placeholders like {0} and {1} exactly), verify encoding and
plural/formatting is preserved, and run a quick lint/compile of localization
assets to ensure the new translations are picked up by functions that render the
bouncer networks UI.

In `@src/locales/fr/messages.mjs`:
- Line 1: The messages catalog contains an untranslated entry "STmlpb" ("Back to
network list"); update the French translation for message ID STmlpb in the
source .po locale (and then regenerate the messages.mjs so messages includes the
French string) — e.g. set STmlpb to "Retour à la liste des réseaux" in the FR
.po and run the locale build that produces the messages const in messages.mjs.

In `@src/locales/fr/messages.po`:
- Around line 62-64: Update the French .po entries where msgstr is empty for the
new UI strings (e.g. the msgid "{0} network{1} — pick one to join" and the other
occurrences noted around lines 387-390 and 1770-1773) by supplying proper French
translations and preserving interpolation placeholders ({0}, {1}) and
punctuation/emdash exactly as in the msgid; ensure each corresponding msgstr is
filled in the same file (messages.po) so the fr locale no longer falls back to
English.

In `@src/locales/it/messages.po`:
- Line 63: Fill the empty Italian translations in the messages.po entries for
the new bouncer UI by replacing the empty msgstr "" values with accurate Italian
translations that match the corresponding msgid source strings; update each
affected msgstr (the entries around the currently empty msgstr at the reported
locations) ensuring proper escaping and plural forms if present, keep the
meaning and UI tone consistent with other Italian locale strings, and then run
the PO linter/validation (or msgfmt) to verify there are no syntax or encoding
errors.

In `@src/locales/ja/messages.mjs`:
- Line 1: The locale entry for message ID STmlpb is still English ("Back to
network list"); add its Japanese translation (e.g. "ネットワークリストに戻る") to the source
.po for Japanese and then regenerate the compiled locale bundle so messages.mjs
includes the new translated value for STmlpb; ensure the updated messages.mjs
contains STmlpb mapped to the Japanese string used by the bouncer networks
panel.

In `@src/locales/ja/messages.po`:
- Line 63: Replace the empty msgstr entries for the new bouncer label/action
msgid entries in the ja messages.po with proper Japanese translations (fill
msgstr for each corresponding msgid), handle any plural forms by populating
msgid_plural and msgstr[N] as appropriate, ensure the file remains UTF-8 encoded
and escapes/quoting match existing PO format, and run your PO linter/msgfmt to
validate the updated src/locales/ja/messages.po before committing.

In `@src/locales/ko/messages.mjs`:
- Line 1: The locale bundle still contains the English string for message ID
STmlpb ("Back to network list"); add the Korean translation for STmlpb in the
Korean .po source (the entry for msgid "Back to network list") and then re-run
the localization build/regeneration step so src/locales/ko/messages.mjs is
regenerated with the new Korean string used by the bouncer networks panel
aria-label.

In `@src/locales/ko/messages.po`:
- Around line 1770-1772: The translation entry for msgid "Open" in the Korean PO
file is empty; update the msgstr for that msgid to the Korean action verb "열기"
so the BouncerNetworksPanel action button is localized consistently (locate the
msgid "Open" entry in the messages.po file and set msgstr "열기").
- Around line 59-63: Add a Korean translation for the msgid "{0} network{1} —
pick one to join" in src/locales/ko/messages.po (used by
BouncerNetworksPanel.tsx): set msgstr to a Korean string that preserves the {0}
placeholder (count) and omits {1} since Korean doesn't use English plural "s"
(for example, "네트워크 {0}개 — 참여할 네트워크를 선택하세요" or similar), ensuring the
placeholder syntax remains unchanged.
- Around line 387-389: Add the missing Korean translation for the msgid "Back to
network list" in src/locales/ko/messages.po by filling in the msgstr for that
entry (the entry referencing src/components/ui/BouncerNetworksPanel.tsx with
msgid "Back to network list"); use the suggested translation (e.g., "← 뒤로 네트워크
목록으로") or another accurate Korean phrase and ensure the msgstr is not empty so
the UI shows the translated label.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d94689ea-4be0-44be-a491-6d0e6d537e16

📥 Commits

Reviewing files that changed from the base of the PR and between 90a4615 and a9c464d.

📒 Files selected for processing (55)
  • src/components/layout/ChatArea.tsx
  • src/components/ui/BouncerNetworkForm.tsx
  • src/components/ui/BouncerNetworksPanel.tsx
  • src/components/ui/LinkSecurityWarningModal.tsx
  • src/lib/irc/IRCClient.ts
  • src/lib/irc/IRCClientContext.ts
  • src/lib/irc/handlers/connection.ts
  • src/locales/cs/messages.mjs
  • src/locales/cs/messages.po
  • src/locales/de/messages.mjs
  • src/locales/de/messages.po
  • src/locales/en/messages.mjs
  • src/locales/en/messages.po
  • src/locales/es/messages.mjs
  • src/locales/es/messages.po
  • src/locales/fi/messages.mjs
  • src/locales/fi/messages.po
  • src/locales/fr/messages.mjs
  • src/locales/fr/messages.po
  • src/locales/it/messages.mjs
  • src/locales/it/messages.po
  • src/locales/ja/messages.mjs
  • src/locales/ja/messages.po
  • src/locales/ko/messages.mjs
  • src/locales/ko/messages.po
  • src/locales/nl/messages.mjs
  • src/locales/nl/messages.po
  • src/locales/pl/messages.mjs
  • src/locales/pl/messages.po
  • src/locales/pt/messages.mjs
  • src/locales/pt/messages.po
  • src/locales/ro/messages.mjs
  • src/locales/ro/messages.po
  • src/locales/ru/messages.mjs
  • src/locales/ru/messages.po
  • src/locales/sv/messages.mjs
  • src/locales/sv/messages.po
  • src/locales/tr/messages.mjs
  • src/locales/tr/messages.po
  • src/locales/uk/messages.mjs
  • src/locales/uk/messages.po
  • src/locales/zh-TW/messages.mjs
  • src/locales/zh-TW/messages.po
  • src/locales/zh/messages.mjs
  • src/locales/zh/messages.po
  • src/store/handlers/auth.ts
  • src/store/handlers/bouncer.ts
  • src/store/index.ts
  • src/types/index.ts
  • tests/components/BouncerNetworkForm.test.tsx
  • tests/components/LinkSecurityWarningModal.test.tsx
  • tests/protocol/bouncerBind.test.ts
  • tests/store/bouncer.test.ts
  • tests/store/bouncerConnect.test.ts
  • tests/store/connectToSavedServers.test.ts
✅ Files skipped from review due to trivial changes (2)
  • src/locales/it/messages.mjs
  • src/locales/cs/messages.mjs

Comment thread src/components/ui/BouncerNetworksPanel.tsx Outdated
Comment on lines 175 to 177
} else if (subcommand === "NAK") {
ctx.sendRaw(serverId, "CAP END");
ctx.sendCapEnd(serverId);
ctx.capNegotiationComplete.set(serverId, true);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Finish registration on CAP NAK.

This branch ends CAP negotiation but never calls ctx.userOnConnect(serverId). If a requested CAP batch is rejected, the client stays after NICK/PASS and never sends USER, unlike the ACK, timeout, and SASL completion paths.

Suggested fix
  } else if (subcommand === "NAK") {
    ctx.sendCapEnd(serverId);
    ctx.capNegotiationComplete.set(serverId, true);
+   ctx.userOnConnect(serverId);
  } else if (subcommand === "NEW") ctx.onCapNew(serverId, caps);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} else if (subcommand === "NAK") {
ctx.sendRaw(serverId, "CAP END");
ctx.sendCapEnd(serverId);
ctx.capNegotiationComplete.set(serverId, true);
} else if (subcommand === "NAK") {
ctx.sendCapEnd(serverId);
ctx.capNegotiationComplete.set(serverId, true);
ctx.userOnConnect(serverId);
} else if (subcommand === "NEW") ctx.onCapNew(serverId, caps);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/irc/handlers/connection.ts` around lines 175 - 177, The CAP NAK
branch currently ends CAP negotiation but doesn't finish registration; after the
existing calls to ctx.sendCapEnd(serverId) and
ctx.capNegotiationComplete.set(serverId, true) in the subcommand === "NAK"
branch, invoke ctx.userOnConnect(serverId) so the client proceeds with the
USER/registration flow (mirror the behavior in the "ACK", timeout, and SASL
completion paths by calling ctx.userOnConnect with the same serverId).

Comment thread src/lib/irc/IRCClient.ts
Comment on lines +649 to +653
// Bouncer child connections share host:port with the control
// connection (and with each other). Scope the pending-connection
// dedup by serverId when one is provided so each child gets its
// own promise rather than landing on a sibling's.
const connectionKey = serverId ?? `${host}:${port}`;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use the same pending-connection key for cleanup.

connect() now stores child-server promises under serverId, but disconnect() still deletes ${server.host}:${server.port}. In the removeServer() path, socket.onclose exits early once the server entry is gone, so that promise can stay stuck in pendingConnections and block later reconnects for the same child server. Please centralize key derivation and reuse it for every set/delete site.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/irc/IRCClient.ts` around lines 649 - 653, The pending-connection key
is derived as serverId ?? `${host}:${port}` in connect(), but
disconnect()/removeServer() still delete `${server.host}:${server.port}` causing
stale promises; centralize key derivation into a single helper (e.g.,
deriveConnectionKey(server, host, port, serverId)) and replace all places that
set/delete pendingConnections (connect(), disconnect(), removeServer(), and
socket.onclose handler) to call that helper so the same connectionKey is used
for both set and delete; ensure the helper is used where connectionKey is
currently declared and where `${server.host}:${server.port}` is constructed.

Resolutions:
- IRCClient.ts CAP REQ list: both branches added a vendor cap entry in
  the same neighbourhood (HEAD added "soju.im/bouncer-networks" + its
  notify variant; main added "obby.world/invitation"). Kept all three.
- store/index.ts type import block: HEAD added BouncerState, main added
  InviteLink. Kept both.
- src/locales/*.po: took ours then re-extracted to merge in main's
  invitations / raw-log / drop-files strings. Backfilled 26 already-
  translated strings × 18 locales by copying msgstrs verbatim from
  obbyworld/main; the remaining 3 (bouncer-UI: "Open", "Back to network
  list", "{0} network{1} — pick one to join") were translated inline.

879 tests + build green; nothing on this branch needed code changes
for the merge itself.
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 5, 2026

Pages Preview
Preview URL: https://bouncer-networks-protocol.obsidianirc.pages.dev

Automated deployment preview for the PR in the Cloudflare Pages.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/locales/de/messages.po (1)

59-63: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

English suffix placeholder {1} is not valid German pluralization.

Line 63 keeps {1} from network{1} (""/"s"), which yields incorrect German forms. Please localize this without English suffix logic (or switch the source message to ICU plural and regenerate catalogs).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/locales/de/messages.po` around lines 59 - 63, The translation is using
the English suffix placeholder `{1}` from msgid "{0} network{1} — pick one to
join" which is invalid for German; update the German entry (the msgstr for that
msgid used by BouncerNetworksPanel.tsx) to remove the `{1}`-based English plural
logic and provide a correct German phrasing (or convert the source string to an
ICU plural form and regenerate catalogs so translations can use proper
pluralization). Ensure the msgstr no longer references `{1}` and correctly
handles singular/plural in German.
src/locales/cs/messages.po (1)

59-63: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Avoid English "{1}" suffix pluralization in Czech translation.

Line 63 reuses {1} from English network{1} logic (""/"s"), which can produce malformed Czech output. Please use Czech-safe wording (or ICU pluralization) that does not depend on the English suffix token.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/locales/cs/messages.po` around lines 59 - 63, The Czech translation
reuses the English suffix token "{1}" (from msgid "{0} network{1} — pick one to
join") which produces incorrect Czech output; update the translation in
messages.po for the msgid referenced by BouncerNetworksPanel.tsx to avoid
relying on "{1}" — either provide full Czech plural forms using gettext
pluralization (convert to msgid_plural and supply msgstr[0], msgstr[1],
msgstr[2] with correct Czech forms for singular/plural) or replace the single
msgstr with a Czech-safe phrase that does not use "{1}" (e.g. use a form that
embeds the count like "{0} síť — vyberte jednu pro připojení" for singular and
the appropriate plural variants), ensuring placeholders include only the numeric
{0} and that the component BouncerNetworksPanel.tsx will use the pluralized
entry or ICU pattern.
🧹 Nitpick comments (1)
src/locales/ja/messages.po (1)

59-63: ⚡ Quick win

Don’t carry English plural suffix placeholder into Japanese output.

Line 63 currently renders {0} ネットワーク{1}..., where {1} is the English "s" suffix. In Japanese this produces incorrect mixed-language output for plurals. Prefer a translation that ignores {1} and uses Japanese-native phrasing.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/locales/ja/messages.po` around lines 59 - 63, The Japanese translation
must not include the English plural placeholder {1}; update the msgstr for the
msgid "{0} network{1} — pick one to join" (from
src/components/ui/BouncerNetworksPanel.tsx) to remove {1} and use a natural
Japanese phrasing that incorporates the count (e.g. use "{0}件のネットワークから参加するものを選択"
or "{0} ネットワークから参加するものを選択") so the string only uses the {0} placeholder and no
English plural suffix.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/locales/cs/messages.po`:
- Around line 85-99: Restore UTF-8 Czech translations in the PO catalog by
replacing the mojibake msgstr values with properly encoded Czech text for the
invitation age strings (e.g., change the msgstr for msgid "{0}d ago" from "p�ed
{0} dny" to "před {0} dny", for "{0}h ago" to "před {0} h", and for "{0}m ago"
to "před {0} min"); ensure the file is saved as UTF-8 and scan/apply the same
fix to the other corrupted ranges mentioned (132-135, 597-600, 745-784, 858-873,
933-936, 1334-1375, 1620-1623, 1740-1751, 2544-2555, 2710-2713, 2908-2935) so
all msgstr entries are valid UTF-8 Czech strings and not mojibake.

In `@src/locales/de/messages.po`:
- Around line 597-600: The German .po entries contain mojibake (e.g., msgid
"Click again to confirm" with msgstr "Erneut klicken zum Bestätigen") — open
src/locales/de/messages.po and replace corrupted encodings with proper UTF-8
German strings (e.g., "Erneut klicken zum Bestätigen"); search for other
corrupted msgstr values referenced in the review (around the msgids used by
InvitationsPanel.tsx and any msgids for delete/for/selected/supported) and fix
them similarly so all msgstr values use proper umlauts/ß (e.g., löschen, für,
ausgewählt, unterstützt) ensuring the file is saved as UTF-8.

In `@src/locales/es/messages.po`:
- Around line 699-702: The Spanish .po strings contain mojibake (e.g., msgid
"Confirm?" with msgstr "¿Confirmar?")—open the translations for the referenced
keys (e.g., the entry for msgid "Confirm?" used by
src/components/ui/InvitationsPanel.tsx and the other listed msgid ranges),
replace corrupted sequences (¿, ó, ñ, �, etc.) with correct UTF-8 Spanish
characters (¿, ó, ñ, —, etc.), re-save/export the .po file encoded as UTF-8
without BOM, and re-run the localization export to ensure the clean UTF-8
catalog is used in the app.

In `@src/locales/fi/messages.po`:
- Around line 781-784: Replace the mojibake in the Finnish .po translations for
the given msgid entries (e.g., msgid "Create and manage your invite links" in
the block associated with UserSettings.tsx) by updating the corrupted msgstr
values to the correct Finnish phrases (e.g., "Luo ja hallinnoi kutsulinkkejäsi")
and similarly fix the other listed ranges (lines referencing the other msgid
strings). Ensure the tool or script that writes the .po file writes UTF-8 (no
BOM) end-to-end so characters like ä, ö, and å are preserved; verify by opening
the saved .po in a UTF-8-aware editor and running a quick grep for typical
mojibake sequences (ä, ö) to confirm removal.

In `@src/locales/fr/messages.po`:
- Around line 597-599: The French PO strings have mojibake (e.g., msgstr for
msgid "Click again to confirm" and other hunks like occurrences of "Créer",
"réseau", etc. in src/locales/fr/messages.po); restore each affected msgstr to
proper Unicode (e.g., "Cliquez à nouveau pour confirmer" for the shown entry) by
fixing the corrupted characters across the listed hunks (773-783, 870-872,
933-935, 1334-1374, 1620-1622, 1740-1750, 2552-2554, 2710-2712, 2908-2910) and
then correct the import/export process that produced the encoding damage (ensure
UTF-8 read/write, no Latin1/Windows-1252 steps) so future PO updates preserve
accented characters.

In `@src/locales/it/messages.po`:
- Around line 1372-1374: The Italian translation for the InvitationsPanel msgid
contains mojibake (e.g. "è", "�"); open the msgstr for that msgid in
src/locales/it/messages.po (and the other occurrences noted at lines ~2552-2554)
and replace the corrupted byte sequences with the correct Unicode characters
(e.g. "è" for è and "—" for â��) so the string reads: "Lascia il canale vuoto
per un invito generico alla rete. La descrizione è solo per i tuoi appunti —
visibile solo a te in questo elenco."; after fixing, run the same catalog
generation/import step that produced these strings to ensure it preserves UTF‑8
encoding and adjust that pipeline if it reintroduces mojibake.

In `@src/locales/ja/messages.po`:
- Around line 85-99: The Japanese translations in src/locales/ja/messages.po for
msgid "{0}d ago", "{0}h ago", "{0}m ago" (and other listed entries) are
mojibake; restore them by replacing the corrupted msgstr values with the
last-known-good Japanese strings (or retranslate them) and ensure the file is
saved and committed as UTF-8 (no double-encoding, no BOM). Locate the affected
msgid entries (e.g., in InvitationsPanel.tsx references) and update their msgstr
fields to valid UTF-8 Japanese, verify rendering in the UI flows
(invitations/raw-log), and commit the fixed .po file encoded as UTF-8.

---

Outside diff comments:
In `@src/locales/cs/messages.po`:
- Around line 59-63: The Czech translation reuses the English suffix token "{1}"
(from msgid "{0} network{1} — pick one to join") which produces incorrect Czech
output; update the translation in messages.po for the msgid referenced by
BouncerNetworksPanel.tsx to avoid relying on "{1}" — either provide full Czech
plural forms using gettext pluralization (convert to msgid_plural and supply
msgstr[0], msgstr[1], msgstr[2] with correct Czech forms for singular/plural) or
replace the single msgstr with a Czech-safe phrase that does not use "{1}" (e.g.
use a form that embeds the count like "{0} síť — vyberte jednu pro připojení"
for singular and the appropriate plural variants), ensuring placeholders include
only the numeric {0} and that the component BouncerNetworksPanel.tsx will use
the pluralized entry or ICU pattern.

In `@src/locales/de/messages.po`:
- Around line 59-63: The translation is using the English suffix placeholder
`{1}` from msgid "{0} network{1} — pick one to join" which is invalid for
German; update the German entry (the msgstr for that msgid used by
BouncerNetworksPanel.tsx) to remove the `{1}`-based English plural logic and
provide a correct German phrasing (or convert the source string to an ICU plural
form and regenerate catalogs so translations can use proper pluralization).
Ensure the msgstr no longer references `{1}` and correctly handles
singular/plural in German.

---

Nitpick comments:
In `@src/locales/ja/messages.po`:
- Around line 59-63: The Japanese translation must not include the English
plural placeholder {1}; update the msgstr for the msgid "{0} network{1} — pick
one to join" (from src/components/ui/BouncerNetworksPanel.tsx) to remove {1} and
use a natural Japanese phrasing that incorporates the count (e.g. use
"{0}件のネットワークから参加するものを選択" or "{0} ネットワークから参加するものを選択") so the string only uses the
{0} placeholder and no English plural suffix.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 194a6cf5-2b1f-4e72-b4ed-9089cf76fa24

📥 Commits

Reviewing files that changed from the base of the PR and between a9c464d and e00becc.

📒 Files selected for processing (45)
  • src/components/layout/ChatArea.tsx
  • src/lib/irc/IRCClient.ts
  • src/lib/irc/handlers/connection.ts
  • src/lib/irc/handlers/index.ts
  • src/locales/cs/messages.mjs
  • src/locales/cs/messages.po
  • src/locales/de/messages.mjs
  • src/locales/de/messages.po
  • src/locales/en/messages.mjs
  • src/locales/en/messages.po
  • src/locales/es/messages.mjs
  • src/locales/es/messages.po
  • src/locales/fi/messages.mjs
  • src/locales/fi/messages.po
  • src/locales/fr/messages.mjs
  • src/locales/fr/messages.po
  • src/locales/it/messages.mjs
  • src/locales/it/messages.po
  • src/locales/ja/messages.mjs
  • src/locales/ja/messages.po
  • src/locales/ko/messages.mjs
  • src/locales/ko/messages.po
  • src/locales/nl/messages.mjs
  • src/locales/nl/messages.po
  • src/locales/pl/messages.mjs
  • src/locales/pl/messages.po
  • src/locales/pt/messages.mjs
  • src/locales/pt/messages.po
  • src/locales/ro/messages.mjs
  • src/locales/ro/messages.po
  • src/locales/ru/messages.mjs
  • src/locales/ru/messages.po
  • src/locales/sv/messages.mjs
  • src/locales/sv/messages.po
  • src/locales/tr/messages.mjs
  • src/locales/tr/messages.po
  • src/locales/uk/messages.mjs
  • src/locales/uk/messages.po
  • src/locales/zh-TW/messages.mjs
  • src/locales/zh-TW/messages.po
  • src/locales/zh/messages.mjs
  • src/locales/zh/messages.po
  • src/store/handlers/index.ts
  • src/store/index.ts
  • src/types/index.ts
✅ Files skipped from review due to trivial changes (3)
  • src/locales/es/messages.mjs
  • src/locales/fr/messages.mjs
  • src/locales/en/messages.mjs
🚧 Files skipped from review as they are similar to previous changes (10)
  • src/locales/de/messages.mjs
  • src/locales/it/messages.mjs
  • src/lib/irc/handlers/connection.ts
  • src/locales/cs/messages.mjs
  • src/components/layout/ChatArea.tsx
  • src/lib/irc/handlers/index.ts
  • src/locales/ja/messages.mjs
  • src/locales/fi/messages.mjs
  • src/lib/irc/IRCClient.ts
  • src/locales/en/messages.po

Comment on lines 85 to 99
#. placeholder {0}: Math.floor(secs / 86400)
#: src/components/ui/InvitationsPanel.tsx
msgid "{0}d ago"
msgstr "před {0} dny"
msgstr "před {0} dny"

#. placeholder {0}: Math.floor(secs / 3600)
#: src/components/ui/InvitationsPanel.tsx
msgid "{0}h ago"
msgstr "před {0} h"
msgstr "před {0} h"

#. placeholder {0}: Math.floor(secs / 60)
#: src/components/ui/InvitationsPanel.tsx
msgid "{0}m ago"
msgstr "před {0} min"
msgstr "před {0} min"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix UTF-8 corruption in Czech invitation-related translations.

Several updated strings are mojibake and will render unreadable in production (examples: Line 88, Line 599, Line 747, Line 1742, Line 2910). This appears to be an encoding conversion issue during catalog update; please restore these entries as proper UTF-8 before merge.

Representative fixes
-msgstr "p�ed {0} dny"
+msgstr "před {0} dny"

-msgstr "KliknÄe znovu pro potvrzení"
+msgstr "Klikněte znovu pro potvrzení"

-msgstr "Kopírovat"
+msgstr "Kopírovat"

-msgstr "Zatím nebyl zachycen žádný surový IRC provoz. Zkuste se p�ipojit nebo odeslat zprávu."
+msgstr "Zatím nebyl zachycen žádný surový IRC provoz. Zkuste se připojit nebo odeslat zprávu."

Also applies to: 132-135, 597-600, 745-784, 858-873, 933-936, 1334-1375, 1620-1623, 1740-1751, 2544-2555, 2710-2713, 2908-2935

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/locales/cs/messages.po` around lines 85 - 99, Restore UTF-8 Czech
translations in the PO catalog by replacing the mojibake msgstr values with
properly encoded Czech text for the invitation age strings (e.g., change the
msgstr for msgid "{0}d ago" from "pÅ�ed {0} dny" to "před {0} dny", for "{0}h
ago" to "před {0} h", and for "{0}m ago" to "před {0} min"); ensure the file is
saved as UTF-8 and scan/apply the same fix to the other corrupted ranges
mentioned (132-135, 597-600, 745-784, 858-873, 933-936, 1334-1375, 1620-1623,
1740-1751, 2544-2555, 2710-2713, 2908-2935) so all msgstr entries are valid
UTF-8 Czech strings and not mojibake.

Comment on lines 597 to 600
#: src/components/ui/InvitationsPanel.tsx
msgid "Click again to confirm"
msgstr "Erneut klicken zum Bestätigen"
msgstr "Erneut klicken zum Bestätigen"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix mojibake in German invitation strings.

Several changed entries are encoding-corrupted and will display broken text (examples: Line 599 Bestätigen, Line 860 löschen, Line 1374 für, Line 1750 ausgewählt, Line 2554 unterstützt). Please restore valid UTF-8 German strings before merge.

Representative fixes
-msgstr "Erneut klicken zum Bestätigen"
+msgstr "Erneut klicken zum Bestätigen"

-msgstr "Bestätigen?"
+msgstr "Bestätigen?"

-msgstr "Diese Einladung löschen"
+msgstr "Diese Einladung löschen"

-msgstr "Kein Server ausgewählt. Wähle zuerst einen Server in der Seitenleiste; Einladungslinks werden pro Server verwaltet."
+msgstr "Kein Server ausgewählt. Wähle zuerst einen Server in der Seitenleiste; Einladungslinks werden pro Server verwaltet."

Also applies to: 699-702, 858-861, 1372-1375, 1748-1751, 2544-2555

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/locales/de/messages.po` around lines 597 - 600, The German .po entries
contain mojibake (e.g., msgid "Click again to confirm" with msgstr "Erneut
klicken zum Bestätigen") — open src/locales/de/messages.po and replace
corrupted encodings with proper UTF-8 German strings (e.g., "Erneut klicken zum
Bestätigen"); search for other corrupted msgstr values referenced in the review
(around the msgids used by InvitationsPanel.tsx and any msgids for
delete/for/selected/supported) and fix them similarly so all msgstr values use
proper umlauts/ß (e.g., löschen, für, ausgewählt, unterstützt) ensuring the file
is saved as UTF-8.

Comment on lines 699 to 702
#: src/components/ui/InvitationsPanel.tsx
msgid "Confirm?"
msgstr "¿Confirmar?"
msgstr "¿Confirmar?"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix mojibake-corrupted Spanish translations before merge.

Several msgstr values are UTF-8 corrupted (e.g., ¿, ó, ñ, �), so users will see broken text in invite flows and related UI. Please restore these entries from valid Spanish strings and re-export the catalog in UTF-8 to prevent recurrence.

Also applies to: 777-784, 858-873, 1372-1375, 1740-1751, 2552-2555, 2908-2911, 2932-2935

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/locales/es/messages.po` around lines 699 - 702, The Spanish .po strings
contain mojibake (e.g., msgid "Confirm?" with msgstr "¿Confirmar?")—open the
translations for the referenced keys (e.g., the entry for msgid "Confirm?" used
by src/components/ui/InvitationsPanel.tsx and the other listed msgid ranges),
replace corrupted sequences (¿, ó, ñ, �, etc.) with correct UTF-8 Spanish
characters (¿, ó, ñ, —, etc.), re-save/export the .po file encoded as UTF-8
without BOM, and re-run the localization export to ensure the clean UTF-8
catalog is used in the app.

Comment on lines 781 to 784
#: src/components/ui/UserSettings.tsx
msgid "Create and manage your invite links"
msgstr "Luo ja hallinnoi kutsulinkkejäsi"
msgstr "Luo ja hallinnoi kutsulinkkejäsi"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Repair UTF-8 corruption in Finnish locale strings.

This file has mojibake in multiple msgstr values (ä, ö, etc.), which will render unreadable UI text in invitations and adjacent flows. Please replace with correct Finnish translations and ensure the .po write path preserves UTF-8 encoding end-to-end.

Also applies to: 858-861, 933-936, 1372-1375, 1740-1751, 2112-2116, 2544-2555, 2710-2713, 2908-2911

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/locales/fi/messages.po` around lines 781 - 784, Replace the mojibake in
the Finnish .po translations for the given msgid entries (e.g., msgid "Create
and manage your invite links" in the block associated with UserSettings.tsx) by
updating the corrupted msgstr values to the correct Finnish phrases (e.g., "Luo
ja hallinnoi kutsulinkkejäsi") and similarly fix the other listed ranges (lines
referencing the other msgid strings). Ensure the tool or script that writes the
.po file writes UTF-8 (no BOM) end-to-end so characters like ä, ö, and å are
preserved; verify by opening the saved .po in a UTF-8-aware editor and running a
quick grep for typical mojibake sequences (ä, ö) to confirm removal.

Comment on lines 597 to +599
#: src/components/ui/InvitationsPanel.tsx
msgid "Click again to confirm"
msgstr "Cliquez à nouveau pour confirmer"
msgstr "Cliquez à nouveau pour confirmer"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix the mojibake before shipping the French catalog.

Several updated msgstr values in these hunks are already corrupted (Créer, réseau, capturé, capacité, etc.). Because this catalog declares UTF-8, the app will render that garbage verbatim across the invite-link and raw-log UI. Please restore the affected strings with the correct Unicode characters and fix the import/export step that introduced the encoding damage.

Also applies to: 773-783, 870-872, 933-935, 1334-1374, 1620-1622, 1740-1750, 2552-2554, 2710-2712, 2908-2910

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/locales/fr/messages.po` around lines 597 - 599, The French PO strings
have mojibake (e.g., msgstr for msgid "Click again to confirm" and other hunks
like occurrences of "Créer", "réseau", etc. in src/locales/fr/messages.po);
restore each affected msgstr to proper Unicode (e.g., "Cliquez à nouveau pour
confirmer" for the shown entry) by fixing the corrupted characters across the
listed hunks (773-783, 870-872, 933-935, 1334-1374, 1620-1622, 1740-1750,
2552-2554, 2710-2712, 2908-2910) and then correct the import/export process that
produced the encoding damage (ensure UTF-8 read/write, no Latin1/Windows-1252
steps) so future PO updates preserve accented characters.

Comment on lines 1372 to +1374
#: src/components/ui/InvitationsPanel.tsx
msgid "Leave channel blank for a generic network invite. Description is just for your records — visible only to you in this list."
msgstr "Lascia il canale vuoto per un invito generico alla rete. La descrizione è solo per i tuoi appunti visibile solo a te in questo elenco."
msgstr "Lascia il canale vuoto per un invito generico alla rete. La descrizione è solo per i tuoi appunti — visibile solo a te in questo elenco."
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix the mojibake in the Italian invite-link copy.

These msgstr values contain encoding corruption (è, �), so the invite panel will show broken Italian text instead of accented characters. Please restore the intended Unicode text and check the catalog generation/import step that produced these strings.

Also applies to: 2552-2554

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/locales/it/messages.po` around lines 1372 - 1374, The Italian translation
for the InvitationsPanel msgid contains mojibake (e.g. "è", "�"); open the
msgstr for that msgid in src/locales/it/messages.po (and the other occurrences
noted at lines ~2552-2554) and replace the corrupted byte sequences with the
correct Unicode characters (e.g. "è" for è and "—" for â��) so the string
reads: "Lascia il canale vuoto per un invito generico alla rete. La descrizione
è solo per i tuoi appunti — visibile solo a te in questo elenco."; after fixing,
run the same catalog generation/import step that produced these strings to
ensure it preserves UTF‑8 encoding and adjust that pipeline if it reintroduces
mojibake.

Comment on lines 85 to 99
#. placeholder {0}: Math.floor(secs / 86400)
#: src/components/ui/InvitationsPanel.tsx
msgid "{0}d ago"
msgstr "{0}日前"
msgstr "{0}日前"

#. placeholder {0}: Math.floor(secs / 3600)
#: src/components/ui/InvitationsPanel.tsx
msgid "{0}h ago"
msgstr "{0}時間前"
msgstr "{0}時間前"

#. placeholder {0}: Math.floor(secs / 60)
#: src/components/ui/InvitationsPanel.tsx
msgid "{0}m ago"
msgstr "{0}分前"
msgstr "{0}分前"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Restore UTF-8 translations; these msgstr values are mojibake and unreadable.

Line 88, Line 134, Line 599, and many other changed entries are corrupted byte sequences rather than valid Japanese text. This will surface broken UI copy in invitations/raw-log flows and must be reverted/fixed before merge.

Use the last known-good Japanese strings (or retranslate) and ensure the PO file is saved and committed as UTF-8 without double-encoding.

Also applies to: 132-135, 597-600, 699-702, 745-752, 765-776, 858-873, 933-936, 1334-1374, 1620-1623, 1740-1751, 2112-2116, 2544-2555, 2710-2713, 2908-2935

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/locales/ja/messages.po` around lines 85 - 99, The Japanese translations
in src/locales/ja/messages.po for msgid "{0}d ago", "{0}h ago", "{0}m ago" (and
other listed entries) are mojibake; restore them by replacing the corrupted
msgstr values with the last-known-good Japanese strings (or retranslate them)
and ensure the file is saved and committed as UTF-8 (no double-encoding, no
BOM). Locate the affected msgid entries (e.g., in InvitationsPanel.tsx
references) and update their msgstr fields to valid UTF-8 Japanese, verify
rendering in the UI flows (invitations/raw-log), and commit the fixed .po file
encoded as UTF-8.

Six fixes for items mupuf raised against the bouncer-networks test
build (issue #120, 2026-05-21). Items 5, 6, 7, 10, 11, 12, 13 from
his list are general-client concerns that affect every connection,
not bouncer-specific -- tracked separately.

#1 deleteServer wipes every server sharing host:port
   ---------------------------------------------------
   The filter keyed on (host, port) to drop the saved row. Bouncer
   children share their parent's soju endpoint (only bouncerNetid
   distinguishes them), so deleting one bouncer-bound network silently
   removed the parent and every sibling from localStorage. On next
   reload nothing was there and the reconnect path surfaced as
   "Authentication required" spam.
   Fix: key the filter on the unique server id. New regression test
   pins it (deleteServerBouncer.test.ts, 3 cases).

#2 ADDNETWORK shows a spinner forever; user reloads to see the network
   ------------------------------------------------------------------
   The form's optimistic 500ms timer closed itself on no-FAIL, but the
   networks list never refreshed -- soju only pushes a BOUNCER NETWORK
   notification when the `-notify` cap is ACK'd, and some deployments
   don't advertise it. Fire BOUNCER LISTNETWORKS on the success path
   so the new row appears regardless of notify-cap state.

#3 Connect button text was illegible
   ---------------------------------
   The button used `bg-primary` -- a CSS-var-driven class that resolves
   to almost-white in the dark theme, against `text-white`. Switched
   every primary-action button in the bouncer panels to
   `bg-discord-primary` (#5865F2) + `text-white`, matching the
   Invitations panel's convention. Also fixed the never-actually-
   defined `bg-primary-hover` class.

#4 Networks not auto-joined
   ------------------------
   Already addressed by PR #249's server-initiated-JOIN fix, which is
   on this branch via the main-merge. PR #249's handler is now also
   gated on isBouncerControl (see #9 below) so the meta connection
   doesn't try to CHATHISTORY the empty channels soju auto-joins
   there.

#8 No way to set the WebSocket path (mupuf had to nginx-rewrite)
   ------------------------------------------------------------
   ircClient.connect already preserves a custom path when the host
   field contains a URL with one, but it was undiscoverable. Added
   helper text under the Server Host input explaining that a full
   URL like `wss://host:port/socket` is accepted, with a soju
   reverse-proxy callout. Translated across all 18 locales.

#9 "Cannot fetch chat history on bouncer connection" spam
   ------------------------------------------------------
   Soju FAILs every CHATHISTORY against the bouncer-networks meta
   session -- it has no real channels of its own. On resume-from-
   suspend, the reconnect-time loop in connection.ts fired one
   CHATHISTORY per cached channel, producing one error toast each.
   Gate every CHATHISTORY send-site on !isBouncerControl:
     - joinChannel (IRCClient.ts)
     - requestChathistoryBefore (IRCClient.ts)
     - reconnect-loop setTimeout (connection.ts)
     - server-initiated own-JOIN handler (users.ts)
   The bouncer control session is now treated like a cap-less
   server: WHO fires immediately, no CHATHISTORY round-trip.

Build + 882 tests green.
@ValwareIRC ValwareIRC force-pushed the bouncer-networks/protocol branch from dda2562 to 456d998 Compare June 5, 2026 22:26
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
src/locales/it/messages.po (1)

1378-1378: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Fix remaining mojibake in Italian msgstr values.

Line 1378 and Line 2558 still contain corrupted byte sequences (è, �) and will display broken UI copy. Restore proper UTF-8 Italian text and regenerate locale artifacts from the corrected source to avoid reintroducing this.

Also applies to: 2558-2558

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/locales/it/messages.po` at line 1378, The Italian msgstr entries in
messages.po contain mojibake (e.g., "è" and "�"); open the msgstr at the
problematic entries (the string currently reading 'Lascia il canale vuoto per un
invito generico alla rete. La descrizione è solo per i tuoi appunti �
visibile solo a te in questo elenco.') and replace the corrupted sequences with
proper UTF-8 characters ("è" and "—" or "—" if you prefer an em dash), verify
other occurrence(s) such as the entry referenced around line 2558 for the same
replacements, save the file in UTF-8 encoding, then regenerate the locale
artifacts (po->compiled files) so the corrected strings are propagated to the
build.
src/locales/ja/messages.po (1)

88-98: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Restore UTF-8 Japanese translations; many msgstr values are still mojibake.

These entries are corrupted and will render unreadable Japanese in production UI. Please replace them with valid UTF-8 strings and regenerate catalogs (.po.mjs) using an encoding-safe pipeline.

Also applies to: 132-134, 597-599, 699-701, 745-751, 765-779, 858-872, 933-935, 1338-1340, 1376-1378, 1624-1626, 1744-1754, 2116-2119, 2548-2558, 2714-2716, 2912-2914, 2936-2938

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/locales/ja/messages.po` around lines 88 - 98, Replace the mojibake msgstr
values in src/locales/ja/messages.po for the time-ago entries (identify by msgid
"{0}d ago", "{0}h ago", "{0}m ago" and the shown msgstrs like "{0}��",
"{0}���", "{0}��") with correct UTF-8 Japanese translations (e.g.
"{0}日前", "{0}時間前", "{0}分前"), then regenerate the compiled locale catalog (.po →
.mjs) using an encoding-safe pipeline (ensure file read/write uses UTF-8) and
apply the same fixes to the other listed corrupted ranges so the runtime UI
renders proper Japanese.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/locales/fr/messages.mjs`:
- Line 1: The exported const messages uses a serialized JSON string with
mojibake (e.g. corrupted sequences like à and â�) — regenerate the French
translation bundle from the original UTF-8 .po source and replace the
JSON.parse(...) payload so all strings are valid UTF-8; ensure the export step
and any tooling (the code that produces the JSON string in messages, plus the
build/save step) write the file with UTF-8 encoding (no CP1252/Latin-1
conversion or BOM) and verify by opening the generated messages string (the
argument to JSON.parse in the exported messages constant) to confirm accented
characters (é, ç, à, ô, etc.) are intact.

---

Duplicate comments:
In `@src/locales/it/messages.po`:
- Line 1378: The Italian msgstr entries in messages.po contain mojibake (e.g.,
"è" and "�"); open the msgstr at the problematic entries (the string
currently reading 'Lascia il canale vuoto per un invito generico alla rete. La
descrizione è solo per i tuoi appunti � visibile solo a te in questo
elenco.') and replace the corrupted sequences with proper UTF-8 characters ("è"
and "—" or "—" if you prefer an em dash), verify other occurrence(s) such as the
entry referenced around line 2558 for the same replacements, save the file in
UTF-8 encoding, then regenerate the locale artifacts (po->compiled files) so the
corrected strings are propagated to the build.

In `@src/locales/ja/messages.po`:
- Around line 88-98: Replace the mojibake msgstr values in
src/locales/ja/messages.po for the time-ago entries (identify by msgid "{0}d
ago", "{0}h ago", "{0}m ago" and the shown msgstrs like "{0}��",
"{0}���", "{0}��") with correct UTF-8 Japanese translations (e.g.
"{0}日前", "{0}時間前", "{0}分前"), then regenerate the compiled locale catalog (.po →
.mjs) using an encoding-safe pipeline (ensure file read/write uses UTF-8) and
apply the same fixes to the other listed corrupted ranges so the runtime UI
renders proper Japanese.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7257e050-5866-4963-83ec-b6c5ed3216bc

📥 Commits

Reviewing files that changed from the base of the PR and between e00becc and 456d998.

📒 Files selected for processing (46)
  • src/components/ui/AddServerModal.tsx
  • src/components/ui/BouncerNetworkForm.tsx
  • src/components/ui/BouncerNetworksPanel.tsx
  • src/lib/irc/IRCClient.ts
  • src/locales/cs/messages.mjs
  • src/locales/cs/messages.po
  • src/locales/de/messages.mjs
  • src/locales/de/messages.po
  • src/locales/en/messages.mjs
  • src/locales/en/messages.po
  • src/locales/es/messages.mjs
  • src/locales/es/messages.po
  • src/locales/fi/messages.mjs
  • src/locales/fi/messages.po
  • src/locales/fr/messages.mjs
  • src/locales/fr/messages.po
  • src/locales/it/messages.mjs
  • src/locales/it/messages.po
  • src/locales/ja/messages.mjs
  • src/locales/ja/messages.po
  • src/locales/ko/messages.mjs
  • src/locales/ko/messages.po
  • src/locales/nl/messages.mjs
  • src/locales/nl/messages.po
  • src/locales/pl/messages.mjs
  • src/locales/pl/messages.po
  • src/locales/pt/messages.mjs
  • src/locales/pt/messages.po
  • src/locales/ro/messages.mjs
  • src/locales/ro/messages.po
  • src/locales/ru/messages.mjs
  • src/locales/ru/messages.po
  • src/locales/sv/messages.mjs
  • src/locales/sv/messages.po
  • src/locales/tr/messages.mjs
  • src/locales/tr/messages.po
  • src/locales/uk/messages.mjs
  • src/locales/uk/messages.po
  • src/locales/zh-TW/messages.mjs
  • src/locales/zh-TW/messages.po
  • src/locales/zh/messages.mjs
  • src/locales/zh/messages.po
  • src/store/handlers/connection.ts
  • src/store/handlers/users.ts
  • src/store/index.ts
  • tests/store/deleteServerBouncer.test.ts
✅ Files skipped from review due to trivial changes (5)
  • src/locales/cs/messages.mjs
  • src/locales/en/messages.mjs
  • src/locales/fi/messages.mjs
  • src/locales/it/messages.mjs
  • src/locales/en/messages.po
🚧 Files skipped from review as they are similar to previous changes (7)
  • src/locales/es/messages.mjs
  • src/locales/de/messages.mjs
  • src/components/ui/BouncerNetworkForm.tsx
  • src/components/ui/BouncerNetworksPanel.tsx
  • src/locales/fi/messages.po
  • src/locales/de/messages.po
  • src/locales/cs/messages.po

Comment thread src/locales/fr/messages.mjs Outdated
@@ -1 +1 @@
/*eslint-disable*/export const messages=JSON.parse("{\"+5VMrz\":[\"Format de modèle invalide. Utilisez le format nick!user@host (jokers * autorisés)\"],\"+6NQQA\":[\"Canal d'assistance générale\"],\"+6NyRG\":[\"Client\"],\"+K0AvT\":[\"Déconnecter\"],\"+cyFdH\":[\"Message par défaut pour le statut absent\"],\"+mVPqU\":[\"Afficher le formatage Markdown dans les messages\"],\"+vqCJH\":[\"Votre nom d'utilisateur de compte pour l'authentification\"],\"+yPBXI\":[\"Choisir un fichier\"],\"+zy2Nq\":[\"Type\"],\"/09cao\":[\"Faible sécurité du lien (niveau \",[\"securityLevel\"],\")\"],\"/3BQ4J\":[\"Les utilisateurs extérieurs ne peuvent pas envoyer de messages\"],\"/4C8U0\":[\"Tout copier\"],\"/6BzZF\":[\"Afficher/masquer la liste des membres\"],\"/AkXyp\":[\"Confirmer ?\"],\"/TNOPk\":[\"L'utilisateur est absent\"],\"/XQgft\":[\"Découvrir\"],\"/cF7Rs\":[\"Volume\"],\"/dqduX\":[\"Page suivante\"],\"/fc3q4\":[\"Tout le contenu\"],\"/kISDh\":[\"Activer les sons de notification\"],\"/n04sB\":[\"Kill\"],\"/rTz0M\":[\"Audio\"],\"/rfkZe\":[\"Jouer des sons pour les mentions et messages\"],\"0/0ZGA\":[\"Masque du nom de salon\"],\"0D6j7U\":[\"En savoir plus sur les règles personnalisées →\"],\"0XsHcR\":[\"Expulser l'utilisateur\"],\"0ZpE//\":[\"Trier par utilisateurs\"],\"0bEPwz\":[\"Se mettre absent\"],\"0dGkPt\":[\"Développer la liste des canaux\"],\"0gS7M5\":[\"Nom d'affichage\"],\"0kS+M8\":[\"ExempleRÉSEAU\"],\"0rgoY7\":[\"Se connecter uniquement aux serveurs choisis\"],\"0wdd7X\":[\"Rejoindre\"],\"0wkVYx\":[\"Messages privés\"],\"111uHX\":[\"Aperçu du lien\"],\"196EG4\":[\"Supprimer la conversation privée\"],\"1DSr1i\":[\"Créer un compte\"],\"1O/24y\":[\"Afficher/masquer la liste des canaux\"],\"1VPJJ2\":[\"Avertissement de lien externe\"],\"1ZC/dv\":[\"Aucune mention ou message non lu\"],\"1pO1zi\":[\"Le nom du serveur est requis\"],\"1uwfzQ\":[\"Voir le sujet du canal\"],\"268g7c\":[\"Saisir le nom d'affichage\"],\"2F9+AZ\":[\"Aucun trafic IRC brut capturé pour le moment. Essayez de vous connecter ou d'envoyer un message.\"],\"2FOFq1\":[\"Les opérateurs du réseau pourraient potentiellement lire vos messages\"],\"2FYpfJ\":[\"Plus\"],\"2HF1Y2\":[[\"inviter\"],\" a invité \",[\"target\"],\" à rejoindre \",[\"channel\"]],\"2I70QL\":[\"Voir les informations du profil utilisateur\"],\"2QYdmE\":[\"Utilisateurs :\"],\"2QpEjG\":[\"a quitté\"],\"2YE223\":[\"Message dans #\",[\"0\"],\" (Entrée pour nouvelle ligne, Maj+Entrée pour envoyer)\"],\"2bimFY\":[\"Utiliser le mot de passe du serveur\"],\"2iTmdZ\":[\"Stockage local :\"],\"2odkwe\":[\"Strict – Protection plus agressive\"],\"2uDhbA\":[\"Saisir le nom d'utilisateur à inviter\"],\"2ygf/L\":[\"← Retour\"],\"2zEgxj\":[\"Rechercher des GIFs...\"],\"3RdPhl\":[\"Renommer le canal\"],\"3THokf\":[\"Utilisateur avec droit de parole\"],\"3TSz9S\":[\"Réduire\"],\"3jBDvM\":[\"Nom d'affichage du salon\"],\"3ryuFU\":[\"Rapports de plantage optionnels pour améliorer l'application\"],\"3uBF/8\":[\"Fermer le visualiseur\"],\"3uwW8F\":[\"https://example.com/avatar/\",[\"size\"],\"/channel.jpg\"],\"3xf8Kz\":[\"Entrez le nom du compte...\"],\"4/Rr0R\":[\"Inviter un utilisateur dans le canal actuel\"],\"4EZrJN\":[\"Règles\"],\"4JJtW9\":[\"#overflow\"],\"4NqeT4\":[\"Profil de flood (+F)\"],\"4RZQRK\":[\"Qu'est-ce que vous faites ?\"],\"4hfTrB\":[\"Pseudo\"],\"4n99LO\":[\"Déjà dans \",[\"0\"]],\"4t6vMV\":[\"Passer automatiquement en mode ligne unique pour les messages courts\"],\"4vsHmf\":[\"Temps (min)\"],\"5+INAX\":[\"Surligner les messages qui vous mentionnent\"],\"5R5Pv/\":[\"Nom Oper\"],\"678PKt\":[\"Nom du réseau\"],\"6Aih4U\":[\"Hors ligne\"],\"6CO3WE\":[\"Mot de passe requis pour rejoindre le salon. Laissez vide pour supprimer la clé.\"],\"6HhMs3\":[\"Message de déconnexion\"],\"6V3Ea3\":[\"Copié\"],\"6lGV3K\":[\"Afficher moins\"],\"6yFOEi\":[\"Entrez le mot de passe oper...\"],\"7+IHTZ\":[\"Aucun fichier choisi\"],\"73hrRi\":[\"nick!user@host (ex. : spam*!*@*, *!*@badhost.com)\"],\"7QkKyN\":[\"Envoyer un message privé\"],\"7U1W7c\":[\"Très détendu\"],\"7Y1YQj\":[\"Nom réel :\"],\"7YHArF\":[\"— ouvrir dans le visualiseur\"],\"7fjnVl\":[\"Rechercher des utilisateurs...\"],\"7jL88x\":[\"Supprimer ce message ? Cette action est irréversible.\"],\"7nGhhM\":[\"À quoi pensez-vous ?\"],\"7sEpu1\":[\"Membres — \",[\"0\"]],\"7sNhEz\":[\"Nom d'utilisateur\"],\"8H0Q+x\":[\"En savoir plus sur les profils →\"],\"8Phu0A\":[\"Afficher quand des utilisateurs changent de pseudo\"],\"8XTG9e\":[\"Saisir le mot de passe oper\"],\"8XsV2J\":[\"Réessayer l'envoi\"],\"8ZsakT\":[\"Mot de passe\"],\"8kR84m\":[\"Vous êtes sur le point d'ouvrir un lien externe :\"],\"8lCgih\":[\"Supprimer la règle\"],\"8o3dPc\":[\"Déposez les fichiers pour les téléverser\"],\"8p/xVT\":[[\"0\",\"plural\",{\"one\":[[\"1\"]],\"other\":[[\"2\"]]}]],\"8wRzac\":[[\"joinCount\",\"plural\",{\"one\":[\"a rejoint\"],\"other\":[\"a rejoint \",[\"joinCount\"],\" fois\"]}]],\"9BMLnJ\":[\"Se reconnecter au serveur\"],\"9OEgyT\":[\"Ajouter une réaction\"],\"9PQ8m2\":[\"G-Line (bannissement global)\"],\"9Qs99X\":[\"E-mail :\"],\"9QupBP\":[\"Supprimer le motif\"],\"9bG48P\":[\"Envoi en cours\"],\"9f5f0u\":[\"Questions sur la confidentialité ? Contactez-nous :\"],\"9unqs3\":[\"Absent :\"],\"9v3hwv\":[\"Aucun serveur trouvé.\"],\"9zb2WA\":[\"Connexion en cours\"],\"A1taO8\":[\"Rechercher\"],\"A2adVi\":[\"Envoyer des notifications de frappe\"],\"A9Rhec\":[\"Nom du salon\"],\"AWOSPo\":[\"Zoomer\"],\"AXSpEQ\":[\"Oper à la connexion\"],\"AeXO77\":[\"Compte\"],\"AhNP40\":[\"Avancer\"],\"Ai2U7L\":[\"Hôte\"],\"AjBQnf\":[\"Pseudo modifié\"],\"AmXVh6\":[\"https://example.com/avatar.png\"],\"AnRu/j\":[\"Annuler la réponse\"],\"ApSx0O\":[[\"0\"],\" messages trouvés correspondant à \\\"\",[\"searchQuery\"],\"\\\"\"],\"AxPAXW\":[\"Aucun résultat trouvé\"],\"AyNqAB\":[\"Afficher tous les événements serveur dans le chat\"],\"B/QqGw\":[\"Absent du clavier\"],\"B8AaMI\":[\"Ce champ est obligatoire\"],\"BA2c49\":[\"Le serveur ne supporte pas le filtrage LIST avancé\"],\"BDKt3I\":[[\"0\"],\", \",[\"1\"],\", \",[\"2\"],\" et \",[\"3\"],\" autres sont en train d'écrire...\"],\"BGul2A\":[\"Vous avez des modifications non enregistrées. Voulez-vous vraiment fermer sans enregistrer ?\"],\"BIf9fi\":[\"Votre message de statut\"],\"BPm98R\":[\"Aucun serveur sélectionné. Choisissez d'abord un serveur dans la barre latérale ; les liens d'invitation sont gérés par serveur.\"],\"BZz3md\":[\"Votre site web personnel\"],\"Bgm/H7\":[\"Permettre la saisie sur plusieurs lignes\"],\"BiQIl1\":[\"Épingler cette conversation privée\"],\"BlNZZ2\":[\"Cliquez pour aller au message\"],\"Bowq3c\":[\"Seuls les opérateurs peuvent modifier le sujet\"],\"Btozzp\":[\"Cette image a expiré\"],\"Bycfjm\":[\"Total : \",[\"0\"]],\"C6IBQc\":[\"Copier le JSON complet\"],\"C9L9wL\":[\"Collecte de données\"],\"CDq4wC\":[\"Modérer l'utilisateur\"],\"CHVRxG\":[\"Message à @\",[\"0\"],\" (Maj+Entrée pour nouvelle ligne)\"],\"CN9zdR\":[\"Le nom oper et le mot de passe sont requis\"],\"CW3sYa\":[\"Ajouter la réaction \",[\"emoji\"]],\"CaAkqd\":[\"Afficher les déconnexions\"],\"CbvaYj\":[\"Bannir par pseudo\"],\"CcK+Ft\":[\"PDF\"],\"Ce8q3L\":[\"Sélectionner un canal\"],\"CsekCi\":[\"Normal\"],\"D+NlUC\":[\"Système\"],\"D28t6+\":[\"a rejoint et quitté\"],\"DB8zMK\":[\"Appliquer\"],\"DBcWHr\":[\"Fichier son de notification personnalisé\"],\"DTy9Xw\":[\"Aperçus des médias\"],\"Dj4pSr\":[\"Choisissez un mot de passe sécurisé\"],\"Du+zn+\":[\"Recherche...\"],\"Du2T2f\":[\"Paramètre introuvable\"],\"DwsSVQ\":[\"Appliquer les filtres & Actualiser\"],\"E3W/zd\":[\"Pseudo par défaut\"],\"E6nRW7\":[\"Copier l'URL\"],\"E703RG\":[\"Modes :\"],\"EAeu1Z\":[\"Envoyer l'invitation\"],\"EFKJQT\":[\"Paramètre\"],\"EGPQBv\":[\"Règles de flood personnalisées (+f)\"],\"ELik0r\":[\"Voir la politique de confidentialité complète\"],\"EPbeC2\":[\"Voir ou modifier le sujet du canal\"],\"EQCDNT\":[\"Entrez le nom d'utilisateur oper...\"],\"EUvulZ\":[\"1 message trouvé correspondant à \\\"\",[\"searchQuery\"],\"\\\"\"],\"EatZYJ\":[\"Image suivante\"],\"EdQY6l\":[\"Aucun\"],\"EnqLYU\":[\"Rechercher des serveurs...\"],\"F0OKMc\":[\"Modifier le serveur\"],\"F6Int2\":[\"Activer les surlignages\"],\"FDoLyE\":[\"Utilisateurs max.\"],\"FUU/hZ\":[\"Contrôle la quantité de médias externes chargés dans le chat.\"],\"Fdp03t\":[\"activé\"],\"FfPWR0\":[\"Modal\"],\"FjkaiT\":[\"Dézoomer\"],\"FlqOE9\":[\"Ce que cela signifie :\"],\"FolHNl\":[\"Gérez votre compte et l'authentification\"],\"Fp2Dif\":[\"A quitté le serveur\"],\"G5KmCc\":[\"GZ-Line (Z-Line globale)\"],\"GDs0lz\":[\"<0>Risque :</0> Des informations sensibles (messages, conversations privées, identifiants de connexion) pourraient être exposées aux administrateurs réseau ou à des attaquants positionnés entre les serveurs IRC.\"],\"GR+2I3\":[\"Ajouter un masque d'invitation (ex. nick!*@*, *!*@host.com)\"],\"GRLyMU\":[\"Fermer les notifications serveur détachées\"],\"GdhD7H\":[\"Cliquez à nouveau pour confirmer\"],\"GlHnXw\":[\"Échec du changement de pseudo: \",[\"error\"],\" \",[\"0\"]],\"GswZF3\":[\"Aperçu :\"],\"GtmO8/\":[\"de\"],\"GtuHUQ\":[\"Renommer ce salon sur le serveur. Tous les utilisateurs verront le nouveau nom.\"],\"GuGfFX\":[\"Activer/désactiver la recherche\"],\"GxkJXS\":[\"Téléversement...\"],\"GzbwnK\":[\"A rejoint le canal\"],\"GzsUDB\":[\"Profil étendu\"],\"H/PnT8\":[\"Insérer un emoji\"],\"H6Izzl\":[\"Votre code couleur préféré\"],\"H9jIv+\":[\"Afficher les entrées/sorties\"],\"HAKBY9\":[\"Télécharger des fichiers\"],\"HdE1If\":[\"Canal\"],\"Hk4AW9\":[\"Votre nom d'affichage préféré\"],\"HmHDk7\":[\"Sélectionner un membre\"],\"HrQzPU\":[\"Canaux sur \",[\"networkName\"]],\"I2tXQ5\":[\"Message à @\",[\"0\"],\" (Entrée pour nouvelle ligne, Maj+Entrée pour envoyer)\"],\"I6bw/h\":[\"Bannir l'utilisateur\"],\"I92Z+b\":[\"Activer les notifications\"],\"I9D72S\":[\"Êtes-vous sûr de vouloir supprimer ce message ? Cette action est irréversible.\"],\"IA+1wo\":[\"Afficher quand des utilisateurs sont expulsés des salons\"],\"IDwkJx\":[\"IRC Operator\"],\"ILlU+s\":[\"Info :\"],\"IUwGEM\":[\"Enregistrer les modifications\"],\"IVeGK6\":[[\"0\"],\", \",[\"1\"],\" et \",[\"2\"],\" sont en train d'écrire...\"],\"IgrLD/\":[\"Pause\"],\"Im6JED\":[\"WHISPER\"],\"ImOQa9\":[\"Répondre\"],\"IoHMnl\":[\"La valeur maximale est \",[\"0\"]],\"IvMj+0\":[\"Op\"],\"J28zul\":[\"Connexion en cours...\"],\"J5T9NW\":[\"Informations utilisateur\"],\"J8Y5+z\":[\"Oups ! La réseau s'est divisé ! ⚠️\"],\"JBHkBA\":[\"A quitté le canal\"],\"JCwL0Q\":[\"Saisir une raison (facultatif)\"],\"JFciKP\":[\"Basculer\"],\"JXGkhG\":[\"Changer le nom du canal (opérateurs uniquement)\"],\"JcD7qf\":[\"Plus d'actions\"],\"JdkA+c\":[\"Secret (+s)\"],\"Jmu12l\":[\"Canaux du serveur\"],\"JvQ++s\":[\"Activer le Markdown\"],\"K2jwh/\":[\"Aucune donnée WHOIS disponible\"],\"KAXSwC\":[\"Voice\"],\"KDfTdX\":[\"Supprimer le message\"],\"KKBlUU\":[\"Intégrer\"],\"KM0pLb\":[\"Bienvenue dans le canal !\"],\"KR6W2h\":[\"Ne plus ignorer l'utilisateur\"],\"KV+Bi1\":[\"Sur invitation uniquement (+i)\"],\"KdCtwE\":[\"Nombre de secondes de surveillance de l'activité de flood avant la réinitialisation des compteurs\"],\"Kkezga\":[\"Mot de passe du serveur\"],\"KsiQ/8\":[\"Les utilisateurs doivent être invités pour rejoindre le salon\"],\"L+gB/D\":[\"Informations sur le salon\"],\"LC1a7n\":[\"Le serveur IRC a signalé que ses liens entre serveurs ont un faible niveau de sécurité. Cela signifie que lorsque vos messages sont relayés entre les serveurs IRC du réseau, ils peuvent ne pas être correctement chiffrés ou les certificats SSL/TLS peuvent ne pas être validés correctement.\"],\"LNfLR5\":[\"Afficher les expulsions\"],\"LQb0W/\":[\"Afficher tous les événements\"],\"LU7/yA\":[\"Nom alternatif pour l'affichage. Peut contenir des espaces, emojis et caractères spéciaux. Le vrai nom (\",[\"channelName\"],\") sera toujours utilisé pour les commandes IRC.\"],\"LUb9O7\":[\"Un port de serveur valide est requis\"],\"LV4fT6\":[\"Description (optionnelle, ex. « Bêta-testeurs T3 »)\"],\"Lb+BUl\":[\"https://example.com/avatar.jpg\"],\"LcET2C\":[\"Politique de confidentialité\"],\"LcuSDR\":[\"Gérez les informations de votre profil et vos métadonnées\"],\"LqLS9B\":[\"Afficher les changements de pseudo\"],\"LsDQt2\":[\"Paramètres du canal\"],\"LtI9AS\":[\"Propriétaire\"],\"LuNhhL\":[\"a réagi à ce message\"],\"M/AZNG\":[\"URL de votre image d'avatar\"],\"M/WIer\":[\"Envoyer un message\"],\"M8er/5\":[\"Nom :\"],\"MHk+7g\":[\"Image précédente\"],\"MRorGe\":[\"MP à l'utilisateur\"],\"MVbSGP\":[\"Fenêtre temporelle (secondes)\"],\"MkpcsT\":[\"Vos messages et paramètres sont stockés localement sur votre appareil\"],\"N/hDSy\":[\"Marquer comme bot, généralement 'on' ou vide\"],\"N7TQbE\":[\"Inviter un utilisateur dans \",[\"channelName\"]],\"NCca/o\":[\"Entrez le pseudo par défaut...\"],\"Nqs6B9\":[\"Affiche tous les médias externes. Toute URL peut déclencher une requête vers un serveur inconnu.\"],\"Nt+9O7\":[\"Utiliser WebSocket au lieu de TCP brut\"],\"NxIHzc\":[\"Expulser l'utilisateur\"],\"O+v/cL\":[\"Parcourir tous les canaux du serveur\"],\"ODwSCk\":[\"Envoyer un GIF\"],\"OGQ5kK\":[\"Configurer les sons de notification et les mises en évidence\"],\"OIPt1Z\":[\"Afficher ou masquer la barre latérale de la liste des membres\"],\"OKSNq/\":[\"Très strict\"],\"ONWvwQ\":[\"Téléverser\"],\"OVKoQO\":[\"Votre mot de passe de compte pour l'authentification\"],\"ObsidianIRC - Bringing IRC to the future\":[\"ObsidianIRC - Amener IRC vers le futur\"],\"OhCpra\":[\"Définir un sujet…\"],\"OkltoQ\":[\"Bannir \",[\"username\"],\" par pseudo (l'empêche de rejoindre avec le même pseudo)\"],\"P+t/Te\":[\"Aucune donnée supplémentaire\"],\"P42Wcc\":[\"Sécurisé\"],\"PD38l0\":[\"Aperçu de l'avatar du canal\"],\"PD9mEt\":[\"Saisir un message...\"],\"PPqfdA\":[\"Ouvrir les paramètres de configuration du canal\"],\"PSCjfZ\":[\"Le sujet affiché pour ce salon. Tous les utilisateurs peuvent le voir.\"],\"PZCecv\":[\"Aperçu PDF\"],\"PeLgsC\":[[\"c\",\"plural\",{\"one\":[\"1 fois\"],\"other\":[[\"c\"],\" fois\"]}]],\"PguS2C\":[\"Ajouter un masque d'exception (ex. nick!*@*, *!*@host.com)\"],\"Pil5Ty\":[\"Affichage de \",[\"displayedChannelsCount\"],\" sur \",[\"0\"],\" canaux\"],\"PqhVlJ\":[\"Bannir l'utilisateur (par hostmask)\"],\"Q+chwU\":[\"Nom d'utilisateur :\"],\"Q2QY4/\":[\"Supprimer cette invitation\"],\"Q6hhn8\":[\"Préférences\"],\"QF4a34\":[\"Veuillez saisir un nom d'utilisateur\"],\"QGqSZ2\":[\"Couleur et mise en forme\"],\"QJQd1J\":[\"Modifier le profil\"],\"QSzGDE\":[\"Inactif\"],\"QUlny5\":[\"Bienvenue sur \",[\"0\"],\" !\"],\"Qoq+GP\":[\"Lire la suite\"],\"QuSkCF\":[\"Filtrer les canaux...\"],\"QwUrDZ\":[\"a changé le sujet en : \",[\"topic\"]],\"R0UH07\":[\"Image \",[\"0\"],\" sur \",[\"1\"]],\"R7SsBE\":[\"Couper le son\"],\"R8rf1X\":[\"Cliquez pour définir le sujet\"],\"RArB3D\":[\"a été expulsé de \",[\"channelName\"],\" par \",[\"username\"]],\"RI3cWd\":[\"Découvrez le monde de l'IRC avec ObsidianIRC\"],\"RIfHS5\":[\"Créer un nouveau lien d'invitation\"],\"RMMaN5\":[\"Modéré (+m)\"],\"RWw9Lg\":[\"Fermer la fenêtre\"],\"RZ2BuZ\":[\"L'enregistrement du compte \",[\"account\"],\" nécessite une vérification : \",[\"message\"]],\"RySp6q\":[\"Masquer les commentaires\"],\"SPKQTd\":[\"Le pseudo est requis\"],\"SPVjfj\":[\"Par défaut « aucune raison » si laissé vide\"],\"SQKPvQ\":[\"Inviter un utilisateur\"],\"SkZcl+\":[\"Choisissez un profil de protection contre le flood prédéfini. Ces profils offrent des paramètres de protection équilibrés pour différents cas d'usage.\"],\"Slr+3C\":[\"Utilisateurs min.\"],\"Spnlre\":[\"Vous avez invité \",[\"target\"],\" à rejoindre \",[\"channel\"]],\"T/ckN5\":[\"Ouvrir dans le visualiseur\"],\"T91vKp\":[\"Lire\"],\"TV2Wdu\":[\"Découvrez comment nous gérons vos données et protégeons votre vie privée.\"],\"TgFpwD\":[\"Application en cours...\"],\"TkzSFB\":[\"Aucune modification\"],\"TtserG\":[\"Saisir le vrai nom\"],\"Ttz9J1\":[\"Entrez le mot de passe...\"],\"Tz0i8g\":[\"Paramètres\"],\"U3pytU\":[\"Admin\"],\"UDb2YD\":[\"Réagir\"],\"UE4KO5\":[\"*channel*\"],\"UETAwW\":[\"Vous n'avez encore créé aucun lien d'invitation. Utilisez le formulaire ci-dessus pour créer le premier.\"],\"UGT5vp\":[\"Enregistrer les paramètres\"],\"UV5hLB\":[\"Aucun bannissement trouvé\"],\"Uaj3Nd\":[\"Messages de statut\"],\"Ue3uny\":[\"Par défaut (aucun profil)\"],\"UkARhe\":[\"Normal – Protection standard\"],\"Umn7Cj\":[\"Pas encore de commentaires. Soyez le premier !\"],\"UtUIRh\":[[\"0\"],\" anciens messages\"],\"UwzP+U\":[\"Connexion sécurisée\"],\"V0/A4O\":[\"Propriétaire du canal\"],\"V4qgxE\":[\"Créé avant (min)\"],\"V8yTm6\":[\"Effacer la recherche\"],\"VJMMyz\":[\"ObsidianIRC - L'IRC vers le futur\"],\"VJScHU\":[\"Raison\"],\"VLsmVV\":[\"Couper les notifications\"],\"VbyRUy\":[\"Commentaires\"],\"Vmx0mQ\":[\"Défini par :\"],\"VqnIZz\":[\"Consulter notre politique de confidentialité et nos pratiques en matière de données\"],\"VrMygG\":[\"La longueur minimale est \",[\"0\"]],\"VrnTui\":[\"Vos pronoms, affichés dans votre profil\"],\"W8E3qn\":[\"Compte authentifié\"],\"WAakm9\":[\"Supprimer le canal\"],\"WFxTHC\":[\"Ajouter un masque de bannissement (ex. nick!*@*, *!*@host.com)\"],\"WN1g9F\":[\"L'hôte du serveur est requis\"],\"WRYdXW\":[\"Position audio\"],\"WUOH5B\":[\"Ignorer l'utilisateur\"],\"WWEXnZ\":[[\"0\",\"plural\",{\"one\":[\"Afficher 1 élément de plus\"],\"other\":[\"Afficher \",[\"1\"],\" éléments de plus\"]}]],\"WYxRzo\":[\"Créer et gérer vos liens d'invitation\"],\"Wd38W1\":[\"Laissez le canal vide pour une invitation générique au réseau. La description sert uniquement à vos notes — visible uniquement par vous dans cette liste.\"],\"Weq9zb\":[\"Général\"],\"Wfj7Sk\":[\"Activer ou désactiver les sons de notification\"],\"Wm7gbG\":[\"GitHub :\"],\"WyeHWY\":[\"*spam*\"],\"WzMCru\":[\"Profil de l'utilisateur\"],\"X6S3lt\":[\"Rechercher des paramètres, canaux, serveurs...\"],\"XEHan5\":[\"Continuer quand même\"],\"XI1+wb\":[\"Format invalide\"],\"XIXeuC\":[\"Message à @\",[\"0\"]],\"XMS+k4\":[\"Démarrer un message privé\"],\"XWgxXq\":[\"Album\"],\"Xd7+IT\":[\"Désépingler la conversation privée\"],\"Xm/s+u\":[\"Affichage\"],\"Xp2n93\":[\"Affiche les médias provenant de l'hébergeur de fichiers de confiance de votre serveur. Aucune requête n'est envoyée à des services externes.\"],\"XvjC4F\":[\"Enregistrement...\"],\"Y/qryO\":[\"Aucun utilisateur ne correspond à votre recherche\"],\"YAqRpI\":[\"Enregistrement du compte réussi pour \",[\"account\"],\" : \",[\"message\"]],\"YEfzvP\":[\"Sujet protégé (+t)\"],\"YQOn6a\":[\"Réduire la liste des membres\"],\"YRCoE9\":[\"Opérateur du canal\"],\"YURQaF\":[\"Voir le profil\"],\"YdBSvr\":[\"Contrôler l'affichage des médias et du contenu externe\"],\"Yj6U3V\":[\"Pas de serveur central :\"],\"YjvpGx\":[\"Pronoms\"],\"YqH4l4\":[\"Aucune clé\"],\"YyUPpV\":[\"Compte :\"],\"ZJSWfw\":[\"Message affiché lors de la déconnexion du serveur\"],\"ZR1dJ4\":[\"Invitations\"],\"ZdWg0V\":[\"Ouvrir dans le navigateur\"],\"ZhRBbl\":[\"Rechercher des messages…\"],\"Zmcu3y\":[\"Filtres avancés\"],\"a2/8e5\":[\"Sujet défini après (min)\"],\"aHKcKc\":[\"Page précédente\"],\"aJTbXX\":[\"Mot de passe Oper\"],\"aQryQv\":[\"Le modèle existe déjà\"],\"aW9pLN\":[\"Nombre maximum d'utilisateurs autorisés. Laissez vide pour aucune limite.\"],\"ah4fmZ\":[\"Affiche également des aperçus de YouTube, Vimeo, SoundCloud et autres services connus.\"],\"aifXak\":[\"Aucun média dans ce salon\"],\"ap2zBz\":[\"Détendu\"],\"az8lvo\":[\"Désactivé\"],\"azXSNo\":[\"Développer la liste des membres\"],\"azdliB\":[\"Se connecter à un compte\"],\"b26wlF\":[\"elle/la\"],\"bD/+Ei\":[\"Strict\"],\"bQ6BJn\":[\"Configurez des règles détaillées de protection contre le flood. Chaque règle précise le type d'activité à surveiller et l'action à prendre lorsque les seuils sont dépassés.\"],\"beV7+y\":[\"L'utilisateur recevra une invitation à rejoindre \",[\"channelName\"],\".\"],\"bk84cH\":[\"Message d'absence\"],\"bkHdLj\":[\"Ajouter un serveur IRC\"],\"bmQLn5\":[\"Ajouter une règle\"],\"bwRvnp\":[\"Action\"],\"c8+EVZ\":[\"Compte vérifié\"],\"cGYUlD\":[\"Aucun aperçu de média n'est chargé.\"],\"cLF98o\":[\"Afficher les commentaires (\",[\"commentCount\"],\")\"],\"cLKIDO\":[\"Aucun utilisateur disponible\"],\"cSgpoS\":[\"Épingler la conversation privée\"],\"cde3ce\":[\"Message <0>\",[\"0\"],\"</0>\"],\"chQsxg\":[\"Copier la sortie formatée\"],\"cl/A5J\":[\"Bienvenue sur \",[\"__DEFAULT_IRC_SERVER_NAME__\"],\" !\"],\"cnGeoo\":[\"Supprimer\"],\"coPLXT\":[\"Nous ne stockons pas vos communications IRC sur nos serveurs\"],\"crYH/6\":[\"Lecteur SoundCloud\"],\"d3sis4\":[\"Ajouter un serveur\"],\"d9aN5k\":[\"Retirer \",[\"username\"],\" du canal\"],\"dEgA5A\":[\"Annuler\"],\"dGi1We\":[\"Désépingler cette conversation privée\"],\"dJVuyC\":[\"a quitté \",[\"channelName\"],\" (\",[\"reason\"],\")\"],\"dMtLDE\":[\"à\"],\"dXqxlh\":[\"<0>⚠️ Risque de sécurité !</0> Cette connexion peut être vulnérable à l'interception ou aux attaques de type man-in-the-middle.\"],\"da9Q/R\":[\"Modes du canal modifiés\"],\"dhJN3N\":[\"Afficher les commentaires\"],\"dj2xTE\":[\"Ignorer la notification\"],\"dpCzmC\":[\"Paramètres de protection contre le flood\"],\"e9dQpT\":[\"Voulez-vous ouvrir ce lien dans un nouvel onglet ?\"],\"ePK91l\":[\"Modifier\"],\"eYBDuB\":[\"Téléverser une image ou fournir une URL avec substitution optionnelle \",[\"size\"]],\"edBbee\":[\"Bannir \",[\"username\"],\" par hostmask (l'empêche de rejoindre depuis la même adresse IP/hôte)\"],\"ekfzWq\":[\"Paramètres utilisateur\"],\"elPDWs\":[\"Personnalisez votre expérience du client IRC\"],\"eu2osY\":[\"<0>💡 Recommandation :</0> Ne continuez que si vous faites confiance à ce serveur et que vous comprenez les risques. Évitez de partager des informations sensibles ou des mots de passe via cette connexion.\"],\"euEhbr\":[\"Cliquez pour rejoindre \",[\"channel\"]],\"ez3vLd\":[\"Activer la saisie multiligne\"],\"f0J5Ki\":[\"Les communications entre serveurs peuvent utiliser des connexions non chiffrées\"],\"f9BHJk\":[\"Avertir l'utilisateur\"],\"fDOLLd\":[\"Aucun canal trouvé.\"],\"ffzDkB\":[\"Analyses anonymes :\"],\"fq1GF9\":[\"Afficher quand des utilisateurs se déconnectent du serveur\"],\"gEF57C\":[\"Ce serveur ne prend en charge qu'un seul type de connexion\"],\"gJuLUI\":[\"Liste d'ignorés\"],\"gNzMrk\":[\"Avatar actuel\"],\"gjPWyO\":[\"Entrez votre pseudo...\"],\"gz6UQ3\":[\"Agrandir\"],\"h6razj\":[\"Exclure le masque de nom de salon\"],\"hG6jnw\":[\"Aucun sujet défini\"],\"hG89Ed\":[\"Image\"],\"hYgDIe\":[\"Créer\"],\"hZ6znB\":[\"Port\"],\"ha+Bz5\":[\"ex. : 100:1440\"],\"he3ygx\":[\"Copier\"],\"hehnjM\":[\"Quantité\"],\"hzdLuQ\":[\"Seuls les utilisateurs avec voice ou plus peuvent parler\"],\"i0qMbr\":[\"Accueil\"],\"iDNBZe\":[\"Notifications\"],\"iH8pgl\":[\"Retour\"],\"iL9SZg\":[\"Bannir l'utilisateur (par pseudo)\"],\"iNt+3c\":[\"Retour à l'image\"],\"iQvi+a\":[\"Ne plus m'avertir de la faible sécurité des liens pour ce serveur\"],\"iSLIjg\":[\"Connecter\"],\"iWXkHH\":[\"Halfop\"],\"iZeTtp\":[\"Hôte du serveur\"],\"idD8Ev\":[\"Enregistré\"],\"iivqkW\":[\"Connecté depuis\"],\"ij+Elv\":[\"Aperçu de l'image\"],\"ilIWp7\":[\"Activer/désactiver les notifications\"],\"iuaqvB\":[\"Utilisez * comme joker. Exemples : baduser!*@*, *!*@spammer.com, troll*!*@*\"],\"ixkTse\":[\"Bot\"],\"j2DGR0\":[\"Bannir par masque d'hôte\"],\"jA4uoI\":[\"Sujet :\"],\"jLXxGK\":[\"https://example.com\"],\"jPSk57\":[\"Raison (facultatif)\"],\"jUV7CU\":[\"Téléverser un avatar\"],\"jW5Uwh\":[\"Contrôle la quantité de médias externes chargés. Désactivé / Sûr / Sources fiables / Tout le contenu.\"],\"jXzms5\":[\"Options de pièce jointe\"],\"jZlrte\":[\"Couleur\"],\"jfC/xh\":[\"Contact\"],\"jywMpv\":[\"#new-channel-name\"],\"k112DD\":[\"Charger les anciens messages\"],\"k3ID0F\":[\"Filtrer les membres…\"],\"k65gsE\":[\"Analyse approfondie\"],\"k7Zgob\":[\"Annuler la connexion\"],\"kAVx5h\":[\"Aucune invitation trouvée\"],\"kCLEPU\":[\"Connecté à\"],\"kF5LKb\":[\"Modèles ignorés :\"],\"kGeOx/\":[\"Rejoindre \",[\"0\"]],\"kITKr8\":[\"Chargement des modes du salon...\"],\"kPpPsw\":[\"Vous êtes un IRC Operator\"],\"kWJmRL\":[\"Vous\"],\"kfcRb0\":[\"Avatar\"],\"kjMqSj\":[\"Copier JSON\"],\"krViRy\":[\"Cliquer pour copier en JSON\"],\"ks71ra\":[\"Exceptions\"],\"kw4lRv\":[\"Semi-opérateur du canal\"],\"kxgIRq\":[\"Sélectionnez ou ajoutez un canal pour commencer.\"],\"ky6dWe\":[\"Aperçu de l'avatar\"],\"l+GxCv\":[\"Chargement des canaux...\"],\"l+IUVW\":[\"Vérification du compte réussie pour \",[\"account\"],\" : \",[\"message\"]],\"l/siQz\":[[\"reconnectCount\",\"plural\",{\"one\":[\"s'est reconnecté\"],\"other\":[\"s'est reconnecté \",[\"reconnectCount\"],\" fois\"]}]],\"l1l8sj\":[\"il y a \",[\"0\"],\" j\"],\"l5NhnV\":[\"#canal (optionnel)\"],\"l5jmzx\":[[\"0\"],\" et \",[\"1\"],\" sont en train d'écrire...\"],\"lCF0wC\":[\"Actualiser\"],\"lHy8N5\":[\"Chargement de canaux supplémentaires...\"],\"lasgrr\":[\"utilisé\"],\"lbpf14\":[\"Rejoindre \",[\"value\"]],\"lfFsZ4\":[\"Canaux\"],\"lkNdiH\":[\"Nom de compte\"],\"ln500L\":[\"ObsidianIRC\"],\"lnCMdg\":[\"Téléverser une image\"],\"loQxaJ\":[\"Je suis de retour\"],\"lvfaxv\":[\"ACCUEIL\"],\"m16xKo\":[\"Ajouter\"],\"m8flAk\":[\"Aperçu (pas encore envoyé)\"],\"mEPxTp\":[\"<0>⚠️ Attention !</0> N'ouvrez que des liens provenant de sources fiables. Des liens malveillants peuvent compromettre votre sécurité ou votre vie privée.\"],\"mHGdhG\":[\"Informations sur le serveur\"],\"mHS8lb\":[\"Message dans #\",[\"0\"]],\"mMYBD9\":[\"Large – Portée de protection étendue\"],\"mTGsPd\":[\"Sujet du salon\"],\"mU8j6O\":[\"Pas de messages externes (+n)\"],\"mZp8FL\":[\"Retour automatique à une seule ligne\"],\"mdQu8G\":[\"VotrePseudo\"],\"miSSBQ\":[\"Commentaires (\",[\"commentCount\"],\")\"],\"mvyLSy\":[\"L'utilisateur est authentifié\"],\"mwtcGl\":[\"Fermer les commentaires\"],\"mzI/c+\":[\"Télécharger\"],\"n3fGRk\":[\"défini par \",[\"0\"]],\"nE9jsU\":[\"Détendu – Protection moins agressive\"],\"nNflMD\":[\"Quitter le canal\"],\"nPXkBi\":[\"Chargement des données WHOIS...\"],\"nQnxxF\":[\"Message dans #\",[\"0\"],\" (Maj+Entrée pour nouvelle ligne)\"],\"nWMRxa\":[\"Désépingler\"],\"nkC032\":[\"Aucun profil anti-flood\"],\"o69z4d\":[\"Envoyer un message d'avertissement à \",[\"username\"]],\"o9ylQi\":[\"Recherchez des GIFs pour commencer\"],\"oFGkER\":[\"Avis du serveur\"],\"oOi11l\":[\"Défiler vers le bas\"],\"oPYIL5\":[\"réseau\"],\"oQEzQR\":[\"Nouveau message privé\"],\"oXOSPE\":[\"En ligne\"],\"oal760\":[\"Des attaques man-in-the-middle sur les liens serveur sont possibles\"],\"oeqmmJ\":[\"Sources de confiance\"],\"optX0N\":[\"il y a \",[\"0\"],\" h\"],\"ovBPCi\":[\"Par défaut\"],\"p0Z69r\":[\"Le modèle ne peut pas être vide\"],\"p1KgtK\":[\"Échec du chargement audio\"],\"p59pEv\":[\"Détails supplémentaires\"],\"p7sRI6\":[\"Informer les autres que vous écrivez\"],\"pBm1od\":[\"Canal secret\"],\"pNmiXx\":[\"Votre pseudo par défaut pour tous les serveurs\"],\"pUUo9G\":[\"Nom d'hôte :\"],\"pVGPmz\":[\"Mot de passe du compte\"],\"peNE68\":[\"Permanent\"],\"plhHQt\":[\"Aucune donnée\"],\"pm6+q5\":[\"Avertissement de sécurité\"],\"pn5qSs\":[\"Informations supplémentaires\"],\"q0cR4S\":[\"est maintenant connu sous le nom de **\",[\"newNick\"],\"**\"],\"qFcunY\":[\"Le salon n'apparaîtra pas dans les commandes LIST ou NAMES\"],\"qLpTm/\":[\"Supprimer la réaction \",[\"emoji\"]],\"qVkGWK\":[\"Épingler\"],\"qY8wNa\":[\"Page d'accueil\"],\"qb0xJ7\":[\"Jokers : * correspond à toute séquence, ? à un seul caractère. Exemples : nick!*@*, *!*@host.com, *!*user@*\"],\"qhzpRq\":[\"Clé du salon (+k)\"],\"qtoOYG\":[\"Aucune limite\"],\"r1W2AS\":[\"Image hébergée\"],\"rIPR2O\":[\"Sujet défini avant (min)\"],\"rMMSYo\":[\"La longueur maximale est \",[\"0\"]],\"rWtzQe\":[\"Le réseau s'est divisé et reconnecté. ✅\"],\"rYG2u6\":[\"Veuillez patienter...\"],\"rdUucN\":[\"Aperçu\"],\"rjGI/Q\":[\"Confidentialité\"],\"rk8iDX\":[\"Chargement des GIFs...\"],\"rn6SBY\":[\"Rétablir le son\"],\"s/UKqq\":[\"A été expulsé du canal\"],\"s8cATI\":[\"a rejoint \",[\"channelName\"]],\"sCO9ue\":[\"La connexion à <0>\",[\"serverName\"],\"</0> présente les problèmes de sécurité suivants :\"],\"sGH11W\":[\"Serveur\"],\"sHI1H+\":[\"est maintenant connu sous le nom de **\",[\"newNick\"],\"**\"],\"sJyV04\":[[\"inviter\"],\" vous a invité à rejoindre \",[\"channel\"]],\"sby+1/\":[\"Cliquer pour copier\"],\"sfN25C\":[\"Votre nom réel ou complet\"],\"sliuzR\":[\"Ouvrir le lien\"],\"sqrO9R\":[\"Mentions personnalisées\"],\"sr6RdJ\":[\"Multiligne avec Shift+Entrée\"],\"swrCpB\":[\"Le canal a été renommé de \",[\"oldName\"],\" en \",[\"newName\"],\" par \",[\"user\"],[\"0\"]],\"sxkWRg\":[\"Avancé\"],\"t/YqKh\":[\"Supprimer\"],\"t47eHD\":[\"Votre identifiant unique sur ce serveur\"],\"tAkAh0\":[\"URL avec substitution optionnelle \",[\"size\"],\". Exemple : https://example.com/avatar/\",[\"size\"],\"/channel.jpg\"],\"tXLJS3\":[\"Afficher ou masquer la barre latérale de la liste des canaux\"],\"tfDRzk\":[\"Enregistrer\"],\"tiBsJk\":[\"a quitté \",[\"channelName\"]],\"tt4/UD\":[\"a quitté (\",[\"reason\"],\")\"],\"u0TcnO\":[\"Le pseudo {nick} est déjà utilisé, nouvel essai avec {newNick}\"],\"u0a8B4\":[\"S'authentifier en tant qu'opérateur IRC pour l'accès administratif\"],\"u0rWFU\":[\"Créé après (min)\"],\"u72w3t\":[\"Utilisateurs et modèles à ignorer\"],\"u7jc2L\":[\"a quitté\"],\"uAQUqI\":[\"Statut\"],\"uB85T3\":[\"Échec de l'enregistrement : \",[\"msg\"]],\"uV3DOL\":[\"G-Line\"],\"uW3lLI\":[\"Serveurs IRC :\"],\"ukyW4o\":[\"Vos liens d'invitation\"],\"usSSr/\":[\"Niveau de zoom\"],\"v7uvcf\":[\"Logiciel :\"],\"vE8kb+\":[\"Shift+Entrée pour les nouvelles lignes (Entrée envoie)\"],\"vERlcd\":[\"Profil\"],\"vK0RL8\":[\"Pas de sujet\"],\"vSJd18\":[\"Vidéo\"],\"vXIe7J\":[\"Langue\"],\"vaHYxN\":[\"Vrai nom\"],\"vhjbKr\":[\"Absent\"],\"w4NYox\":[\"client \",[\"title\"]],\"w8xQRx\":[\"Valeur invalide\"],\"wFjjxZ\":[\"a été expulsé de \",[\"channelName\"],\" par \",[\"username\"],\" (\",[\"reason\"],\")\"],\"wGjaGl\":[\"Aucune exception de bannissement trouvée\"],\"wPrGnM\":[\"Administrateur du canal\"],\"wRkP2d\":[\"GIF\"],\"wbm86v\":[\"Afficher quand des utilisateurs rejoignent ou quittent des salons\"],\"whqZ9r\":[\"Mots ou phrases supplémentaires à surligner\"],\"wm7RV4\":[\"Son de notification\"],\"wz/Yoq\":[\"Vos messages pourraient être interceptés lors du relais entre serveurs\"],\"x3+y8b\":[\"Nombre de personnes inscrites via ce lien\"],\"xCJdfg\":[\"Effacer\"],\"xOTzt5\":[\"à l'instant\"],\"xUHRTR\":[\"S'authentifier automatiquement comme opérateur à la connexion\"],\"xWHwwQ\":[\"Bannissements\"],\"xYilR2\":[\"Médias\"],\"xbi8D6\":[\"Ce serveur ne prend pas en charge les liens d'invitation (la capacité<0>obby.world/invitation</0>n'est pas annoncée). Vous pouvez toujours discuter normalement ; ce panneau est destiné aux réseaux propulsés par obbyircd.\"],\"xceQrO\":[\"Seuls les websockets sécurisés sont pris en charge\"],\"xdtXa+\":[\"nom-du-salon\"],\"xfXC7q\":[\"Salons textuels\"],\"xlCYOE\":[\"Chargement des messages...\"],\"xlhswE\":[\"La valeur minimale est \",[\"0\"]],\"xq97Ci\":[\"Ajouter un mot ou une expression...\"],\"xuRqRq\":[\"Limite de clients (+l)\"],\"xwF+7J\":[[\"0\"],\" est en train d'écrire...\"],\"y1eoq1\":[\"Copier le lien\"],\"yNeucF\":[\"Ce serveur ne supporte pas les métadonnées de profil étendues (extension IRCv3 METADATA). Les champs comme l'avatar, le nom d'affichage et le statut ne sont pas disponibles.\"],\"yPlrca\":[\"Avatar du salon\"],\"yQE2r9\":[\"Chargement\"],\"ySU+JY\":[\"votre@email.com\"],\"yTX1Rt\":[\"Nom d'utilisateur opérateur\"],\"yYOzWD\":[\"journaux\"],\"yfx9Re\":[\"Mot de passe opérateur IRC\"],\"ygCKqB\":[\"Arrêter\"],\"ymDxJx\":[\"Nom d'utilisateur opérateur IRC\"],\"yrpRsQ\":[\"Trier par nom\"],\"yz7wBu\":[\"Fermer\"],\"zJw+jA\":[\"définit le mode : \",[\"0\"]],\"zbymaY\":[\"il y a \",[\"0\"],\" min\"],\"zebeLu\":[\"Saisir le nom d'utilisateur oper\"],\"zpr0Bw\":[\"GZ-Line\"]}"); No newline at end of file
/*eslint-disable*/export const messages=JSON.parse("{\"+5VMrz\":[\"Format de modèle invalide. Utilisez le format nick!user@host (jokers * autorisés)\"],\"+6NQQA\":[\"Canal d'assistance générale\"],\"+6NyRG\":[\"Client\"],\"+K0AvT\":[\"Déconnecter\"],\"+cyFdH\":[\"Message par défaut pour le statut absent\"],\"+mVPqU\":[\"Afficher le formatage Markdown dans les messages\"],\"+vqCJH\":[\"Votre nom d'utilisateur de compte pour l'authentification\"],\"+yPBXI\":[\"Choisir un fichier\"],\"+zy2Nq\":[\"Type\"],\"/09cao\":[\"Faible sécurité du lien (niveau \",[\"securityLevel\"],\")\"],\"/3BQ4J\":[\"Les utilisateurs extérieurs ne peuvent pas envoyer de messages\"],\"/4C8U0\":[\"Tout copier\"],\"/6BzZF\":[\"Afficher/masquer la liste des membres\"],\"/AkXyp\":[\"Confirmer ?\"],\"/TNOPk\":[\"L'utilisateur est absent\"],\"/XQgft\":[\"Découvrir\"],\"/cF7Rs\":[\"Volume\"],\"/dqduX\":[\"Page suivante\"],\"/fc3q4\":[\"Tout le contenu\"],\"/kISDh\":[\"Activer les sons de notification\"],\"/n04sB\":[\"Kill\"],\"/rTz0M\":[\"Audio\"],\"/rfkZe\":[\"Jouer des sons pour les mentions et messages\"],\"0/0ZGA\":[\"Masque du nom de salon\"],\"0D6j7U\":[\"En savoir plus sur les règles personnalisées →\"],\"0XsHcR\":[\"Expulser l'utilisateur\"],\"0ZpE//\":[\"Trier par utilisateurs\"],\"0bEPwz\":[\"Se mettre absent\"],\"0dGkPt\":[\"Développer la liste des canaux\"],\"0gS7M5\":[\"Nom d'affichage\"],\"0kS+M8\":[\"ExempleRÉSEAU\"],\"0rgoY7\":[\"Se connecter uniquement aux serveurs choisis\"],\"0wdd7X\":[\"Rejoindre\"],\"0wkVYx\":[\"Messages privés\"],\"111uHX\":[\"Aperçu du lien\"],\"196EG4\":[\"Supprimer la conversation privée\"],\"1DSr1i\":[\"Créer un compte\"],\"1O/24y\":[\"Afficher/masquer la liste des canaux\"],\"1TNIig\":[\"Ouvrir\"],\"1VPJJ2\":[\"Avertissement de lien externe\"],\"1ZC/dv\":[\"Aucune mention ou message non lu\"],\"1pO1zi\":[\"Le nom du serveur est requis\"],\"1uwfzQ\":[\"Voir le sujet du canal\"],\"268g7c\":[\"Saisir le nom d'affichage\"],\"2F9+AZ\":[\"Aucun trafic IRC brut capturé pour le moment. Essayez de vous connecter ou d'envoyer un message.\"],\"2FOFq1\":[\"Les opérateurs du réseau pourraient potentiellement lire vos messages\"],\"2FYpfJ\":[\"Plus\"],\"2HF1Y2\":[[\"inviter\"],\" a invité \",[\"target\"],\" à rejoindre \",[\"channel\"]],\"2I70QL\":[\"Voir les informations du profil utilisateur\"],\"2QYdmE\":[\"Utilisateurs :\"],\"2QpEjG\":[\"a quitté\"],\"2YE223\":[\"Message dans #\",[\"0\"],\" (Entrée pour nouvelle ligne, Maj+Entrée pour envoyer)\"],\"2bimFY\":[\"Utiliser le mot de passe du serveur\"],\"2iTmdZ\":[\"Stockage local :\"],\"2odkwe\":[\"Strict – Protection plus agressive\"],\"2uDhbA\":[\"Saisir le nom d'utilisateur à inviter\"],\"2ygf/L\":[\"← Retour\"],\"2zEgxj\":[\"Rechercher des GIFs...\"],\"3RdPhl\":[\"Renommer le canal\"],\"3THokf\":[\"Utilisateur avec droit de parole\"],\"3TSz9S\":[\"Réduire\"],\"3jBDvM\":[\"Nom d'affichage du salon\"],\"3ryuFU\":[\"Rapports de plantage optionnels pour améliorer l'application\"],\"3uBF/8\":[\"Fermer le visualiseur\"],\"3uwW8F\":[\"https://example.com/avatar/\",[\"size\"],\"/channel.jpg\"],\"3xf8Kz\":[\"Entrez le nom du compte...\"],\"4/Rr0R\":[\"Inviter un utilisateur dans le canal actuel\"],\"4EZrJN\":[\"Règles\"],\"4JJtW9\":[\"#overflow\"],\"4NqeT4\":[\"Profil de flood (+F)\"],\"4RZQRK\":[\"Qu'est-ce que vous faites ?\"],\"4hfTrB\":[\"Pseudo\"],\"4n99LO\":[\"Déjà dans \",[\"0\"]],\"4t6vMV\":[\"Passer automatiquement en mode ligne unique pour les messages courts\"],\"4vsHmf\":[\"Temps (min)\"],\"4x/Axu\":[\"Votre bouncer n'a aucun réseau pour le moment. Ajoutez-en un pour commencer.\"],\"5+INAX\":[\"Surligner les messages qui vous mentionnent\"],\"5R5Pv/\":[\"Nom Oper\"],\"678PKt\":[\"Nom du réseau\"],\"6Aih4U\":[\"Hors ligne\"],\"6CO3WE\":[\"Mot de passe requis pour rejoindre le salon. Laissez vide pour supprimer la clé.\"],\"6HhMs3\":[\"Message de déconnexion\"],\"6V3Ea3\":[\"Copié\"],\"6lGV3K\":[\"Afficher moins\"],\"6yFOEi\":[\"Entrez le mot de passe oper...\"],\"7+IHTZ\":[\"Aucun fichier choisi\"],\"73fnil\":[\"TLS\"],\"73hrRi\":[\"nick!user@host (ex. : spam*!*@*, *!*@badhost.com)\"],\"7QkKyN\":[\"Envoyer un message privé\"],\"7U1W7c\":[\"Très détendu\"],\"7Y1YQj\":[\"Nom réel :\"],\"7YHArF\":[\"— ouvrir dans le visualiseur\"],\"7fjnVl\":[\"Rechercher des utilisateurs...\"],\"7jL88x\":[\"Supprimer ce message ? Cette action est irréversible.\"],\"7nGhhM\":[\"À quoi pensez-vous ?\"],\"7sEpu1\":[\"Membres — \",[\"0\"]],\"7sNhEz\":[\"Nom d'utilisateur\"],\"8H0Q+x\":[\"En savoir plus sur les profils →\"],\"8Phu0A\":[\"Afficher quand des utilisateurs changent de pseudo\"],\"8XTG9e\":[\"Saisir le mot de passe oper\"],\"8XsV2J\":[\"Réessayer l'envoi\"],\"8ZsakT\":[\"Mot de passe\"],\"8kR84m\":[\"Vous êtes sur le point d'ouvrir un lien externe :\"],\"8lCgih\":[\"Supprimer la règle\"],\"8o3dPc\":[\"Déposez les fichiers pour les téléverser\"],\"8p/xVT\":[[\"0\",\"plural\",{\"one\":[[\"1\"]],\"other\":[[\"2\"]]}]],\"8wRzac\":[[\"joinCount\",\"plural\",{\"one\":[\"a rejoint\"],\"other\":[\"a rejoint \",[\"joinCount\"],\" fois\"]}]],\"9BMLnJ\":[\"Se reconnecter au serveur\"],\"9OEgyT\":[\"Ajouter une réaction\"],\"9PQ8m2\":[\"G-Line (bannissement global)\"],\"9Qs99X\":[\"E-mail :\"],\"9QupBP\":[\"Supprimer le motif\"],\"9W7tl5\":[\"(inchangé)\"],\"9bG48P\":[\"Envoi en cours\"],\"9f5f0u\":[\"Questions sur la confidentialité ? Contactez-nous :\"],\"9iweoP\":[\"Réseaux sur \",[\"0\"]],\"9unqs3\":[\"Absent :\"],\"9v3hwv\":[\"Aucun serveur trouvé.\"],\"9zb2WA\":[\"Connexion en cours\"],\"A1taO8\":[\"Rechercher\"],\"A2adVi\":[\"Envoyer des notifications de frappe\"],\"A9Rhec\":[\"Nom du salon\"],\"AWOSPo\":[\"Zoomer\"],\"AXSpEQ\":[\"Oper à la connexion\"],\"AeXO77\":[\"Compte\"],\"AhNP40\":[\"Avancer\"],\"Ai2U7L\":[\"Hôte\"],\"AjBQnf\":[\"Pseudo modifié\"],\"AmXVh6\":[\"https://example.com/avatar.png\"],\"AnRu/j\":[\"Annuler la réponse\"],\"ApSx0O\":[[\"0\"],\" messages trouvés correspondant à \\\"\",[\"searchQuery\"],\"\\\"\"],\"AxPAXW\":[\"Aucun résultat trouvé\"],\"AyNqAB\":[\"Afficher tous les événements serveur dans le chat\"],\"B/QqGw\":[\"Absent du clavier\"],\"B0sB2k\":[\"Texte en clair\"],\"B8AaMI\":[\"Ce champ est obligatoire\"],\"BA2c49\":[\"Le serveur ne supporte pas le filtrage LIST avancé\"],\"BDKt3I\":[[\"0\"],\", \",[\"1\"],\", \",[\"2\"],\" et \",[\"3\"],\" autres sont en train d'écrire...\"],\"BGul2A\":[\"Vous avez des modifications non enregistrées. Voulez-vous vraiment fermer sans enregistrer ?\"],\"BIf9fi\":[\"Votre message de statut\"],\"BPm98R\":[\"Aucun serveur sélectionné. Choisissez d'abord un serveur dans la barre latérale ; les liens d'invitation sont gérés par serveur.\"],\"BZz3md\":[\"Votre site web personnel\"],\"Bgm/H7\":[\"Permettre la saisie sur plusieurs lignes\"],\"BiQIl1\":[\"Épingler cette conversation privée\"],\"BlNZZ2\":[\"Cliquez pour aller au message\"],\"Bowq3c\":[\"Seuls les opérateurs peuvent modifier le sujet\"],\"Btozzp\":[\"Cette image a expiré\"],\"Bycfjm\":[\"Total : \",[\"0\"]],\"C6IBQc\":[\"Copier le JSON complet\"],\"C9L9wL\":[\"Collecte de données\"],\"CDq4wC\":[\"Modérer l'utilisateur\"],\"CHVRxG\":[\"Message à @\",[\"0\"],\" (Maj+Entrée pour nouvelle ligne)\"],\"CN9zdR\":[\"Le nom oper et le mot de passe sont requis\"],\"CW3sYa\":[\"Ajouter la réaction \",[\"emoji\"]],\"CaAkqd\":[\"Afficher les déconnexions\"],\"CbvaYj\":[\"Bannir par pseudo\"],\"CcK+Ft\":[\"PDF\"],\"Ce8q3L\":[\"Sélectionner un canal\"],\"CsekCi\":[\"Normal\"],\"D+NlUC\":[\"Système\"],\"D28t6+\":[\"a rejoint et quitté\"],\"DB8zMK\":[\"Appliquer\"],\"DBcWHr\":[\"Fichier son de notification personnalisé\"],\"DTy9Xw\":[\"Aperçus des médias\"],\"Dj4pSr\":[\"Choisissez un mot de passe sécurisé\"],\"Du+zn+\":[\"Recherche...\"],\"Du2T2f\":[\"Paramètre introuvable\"],\"DwsSVQ\":[\"Appliquer les filtres & Actualiser\"],\"E3W/zd\":[\"Pseudo par défaut\"],\"E6nRW7\":[\"Copier l'URL\"],\"E703RG\":[\"Modes :\"],\"EAeu1Z\":[\"Envoyer l'invitation\"],\"EFKJQT\":[\"Paramètre\"],\"EGPQBv\":[\"Règles de flood personnalisées (+f)\"],\"ELik0r\":[\"Voir la politique de confidentialité complète\"],\"EPbeC2\":[\"Voir ou modifier le sujet du canal\"],\"EQCDNT\":[\"Entrez le nom d'utilisateur oper...\"],\"EUvulZ\":[\"1 message trouvé correspondant à \\\"\",[\"searchQuery\"],\"\\\"\"],\"EatZYJ\":[\"Image suivante\"],\"EdQY6l\":[\"Aucun\"],\"EnqLYU\":[\"Rechercher des serveurs...\"],\"F0OKMc\":[\"Modifier le serveur\"],\"F6Int2\":[\"Activer les surlignages\"],\"FDoLyE\":[\"Utilisateurs max.\"],\"FUU/hZ\":[\"Contrôle la quantité de médias externes chargés dans le chat.\"],\"Fdp03t\":[\"activé\"],\"FfPWR0\":[\"Modal\"],\"FjkaiT\":[\"Dézoomer\"],\"FlqOE9\":[\"Ce que cela signifie :\"],\"FolHNl\":[\"Gérez votre compte et l'authentification\"],\"Fp2Dif\":[\"A quitté le serveur\"],\"G5KmCc\":[\"GZ-Line (Z-Line globale)\"],\"GDs0lz\":[\"<0>Risque :</0> Des informations sensibles (messages, conversations privées, identifiants de connexion) pourraient être exposées aux administrateurs réseau ou à des attaquants positionnés entre les serveurs IRC.\"],\"GR+2I3\":[\"Ajouter un masque d'invitation (ex. nick!*@*, *!*@host.com)\"],\"GRLyMU\":[\"Fermer les notifications serveur détachées\"],\"GdhD7H\":[\"Cliquez Ã\xA0 nouveau pour confirmer\"],\"GlHnXw\":[\"Échec du changement de pseudo: \",[\"error\"],\" \",[\"0\"]],\"GswZF3\":[\"Aperçu :\"],\"GtmO8/\":[\"de\"],\"GtuHUQ\":[\"Renommer ce salon sur le serveur. Tous les utilisateurs verront le nouveau nom.\"],\"GuGfFX\":[\"Activer/désactiver la recherche\"],\"GxkJXS\":[\"Téléversement...\"],\"GzbwnK\":[\"A rejoint le canal\"],\"GzsUDB\":[\"Profil étendu\"],\"H/PnT8\":[\"Insérer un emoji\"],\"H6Izzl\":[\"Votre code couleur préféré\"],\"H9jIv+\":[\"Afficher les entrées/sorties\"],\"HAKBY9\":[\"Télécharger des fichiers\"],\"HdE1If\":[\"Canal\"],\"Hk4AW9\":[\"Votre nom d'affichage préféré\"],\"HmHDk7\":[\"Sélectionner un membre\"],\"HrQzPU\":[\"Canaux sur \",[\"networkName\"]],\"I2tXQ5\":[\"Message à @\",[\"0\"],\" (Entrée pour nouvelle ligne, Maj+Entrée pour envoyer)\"],\"I6bw/h\":[\"Bannir l'utilisateur\"],\"I92Z+b\":[\"Activer les notifications\"],\"I9D72S\":[\"Êtes-vous sûr de vouloir supprimer ce message ? Cette action est irréversible.\"],\"IA+1wo\":[\"Afficher quand des utilisateurs sont expulsés des salons\"],\"IDwkJx\":[\"IRC Operator\"],\"ILlU+s\":[\"Info :\"],\"IUwGEM\":[\"Enregistrer les modifications\"],\"IVeGK6\":[[\"0\"],\", \",[\"1\"],\" et \",[\"2\"],\" sont en train d'écrire...\"],\"IgrLD/\":[\"Pause\"],\"Im6JED\":[\"WHISPER\"],\"ImOQa9\":[\"Répondre\"],\"IoHMnl\":[\"La valeur maximale est \",[\"0\"]],\"IvMj+0\":[\"Op\"],\"J28zul\":[\"Connexion en cours...\"],\"J5T9NW\":[\"Informations utilisateur\"],\"J8Y5+z\":[\"Oups ! La réseau s'est divisé ! ⚠️\"],\"JBHkBA\":[\"A quitté le canal\"],\"JCwL0Q\":[\"Saisir une raison (facultatif)\"],\"JFciKP\":[\"Basculer\"],\"JXGkhG\":[\"Changer le nom du canal (opérateurs uniquement)\"],\"JcD7qf\":[\"Plus d'actions\"],\"JdkA+c\":[\"Secret (+s)\"],\"Jmu12l\":[\"Canaux du serveur\"],\"JvQ++s\":[\"Activer le Markdown\"],\"K2jwh/\":[\"Aucune donnée WHOIS disponible\"],\"KAXSwC\":[\"Voice\"],\"KDfTdX\":[\"Supprimer le message\"],\"KKBlUU\":[\"Intégrer\"],\"KM0pLb\":[\"Bienvenue dans le canal !\"],\"KR6W2h\":[\"Ne plus ignorer l'utilisateur\"],\"KV+Bi1\":[\"Sur invitation uniquement (+i)\"],\"KdCtwE\":[\"Nombre de secondes de surveillance de l'activité de flood avant la réinitialisation des compteurs\"],\"Kkezga\":[\"Mot de passe du serveur\"],\"KsiQ/8\":[\"Les utilisateurs doivent être invités pour rejoindre le salon\"],\"L+gB/D\":[\"Informations sur le salon\"],\"LC1a7n\":[\"Le serveur IRC a signalé que ses liens entre serveurs ont un faible niveau de sécurité. Cela signifie que lorsque vos messages sont relayés entre les serveurs IRC du réseau, ils peuvent ne pas être correctement chiffrés ou les certificats SSL/TLS peuvent ne pas être validés correctement.\"],\"LNfLR5\":[\"Afficher les expulsions\"],\"LP+1Z7\":[\"Ajouter un réseau\"],\"LQb0W/\":[\"Afficher tous les événements\"],\"LU7/yA\":[\"Nom alternatif pour l'affichage. Peut contenir des espaces, emojis et caractères spéciaux. Le vrai nom (\",[\"channelName\"],\") sera toujours utilisé pour les commandes IRC.\"],\"LUb9O7\":[\"Un port de serveur valide est requis\"],\"LV4fT6\":[\"Description (optionnelle, ex. « Bêta-testeurs T3 »)\"],\"Lb+BUl\":[\"https://example.com/avatar.jpg\"],\"LcET2C\":[\"Politique de confidentialité\"],\"LcuSDR\":[\"Gérez les informations de votre profil et vos métadonnées\"],\"LqLS9B\":[\"Afficher les changements de pseudo\"],\"LsDQt2\":[\"Paramètres du canal\"],\"LtI9AS\":[\"Propriétaire\"],\"LuNhhL\":[\"a réagi à ce message\"],\"M/AZNG\":[\"URL de votre image d'avatar\"],\"M/WIer\":[\"Envoyer un message\"],\"M8er/5\":[\"Nom :\"],\"MHk+7g\":[\"Image précédente\"],\"MRorGe\":[\"MP à l'utilisateur\"],\"MVbSGP\":[\"Fenêtre temporelle (secondes)\"],\"MkpcsT\":[\"Vos messages et paramètres sont stockés localement sur votre appareil\"],\"MzPdC2\":[\"Mot de passe du serveur (PASS)\"],\"N/hDSy\":[\"Marquer comme bot, généralement 'on' ou vide\"],\"N6j2JH\":[\"Modifier \",[\"0\"]],\"N7TQbE\":[\"Inviter un utilisateur dans \",[\"channelName\"]],\"NCca/o\":[\"Entrez le pseudo par défaut...\"],\"Nqs6B9\":[\"Affiche tous les médias externes. Toute URL peut déclencher une requête vers un serveur inconnu.\"],\"Nt+9O7\":[\"Utiliser WebSocket au lieu de TCP brut\"],\"NxIHzc\":[\"Expulser l'utilisateur\"],\"O+v/cL\":[\"Parcourir tous les canaux du serveur\"],\"OCGpR4\":[\"(hériter)\"],\"ODwSCk\":[\"Envoyer un GIF\"],\"OGQ5kK\":[\"Configurer les sons de notification et les mises en évidence\"],\"OIPt1Z\":[\"Afficher ou masquer la barre latérale de la liste des membres\"],\"OKSNq/\":[\"Très strict\"],\"ONWvwQ\":[\"Téléverser\"],\"OVKoQO\":[\"Votre mot de passe de compte pour l'authentification\"],\"ObsidianIRC - Bringing IRC to the future\":[\"ObsidianIRC - Amener IRC vers le futur\"],\"OhCpra\":[\"Définir un sujet…\"],\"OkltoQ\":[\"Bannir \",[\"username\"],\" par pseudo (l'empêche de rejoindre avec le même pseudo)\"],\"P+t/Te\":[\"Aucune donnée supplémentaire\"],\"P42Wcc\":[\"Sécurisé\"],\"PD38l0\":[\"Aperçu de l'avatar du canal\"],\"PD9mEt\":[\"Saisir un message...\"],\"PPqfdA\":[\"Ouvrir les paramètres de configuration du canal\"],\"PSCjfZ\":[\"Le sujet affiché pour ce salon. Tous les utilisateurs peuvent le voir.\"],\"PZCecv\":[\"Aperçu PDF\"],\"PeLgsC\":[[\"c\",\"plural\",{\"one\":[\"1 fois\"],\"other\":[[\"c\"],\" fois\"]}]],\"PguS2C\":[\"Ajouter un masque d'exception (ex. nick!*@*, *!*@host.com)\"],\"Pil5Ty\":[\"Affichage de \",[\"displayedChannelsCount\"],\" sur \",[\"0\"],\" canaux\"],\"PqhVlJ\":[\"Bannir l'utilisateur (par hostmask)\"],\"Q+chwU\":[\"Nom d'utilisateur :\"],\"Q2QY4/\":[\"Supprimer cette invitation\"],\"Q3v9Wc\":[\"Oui, supprimer\"],\"Q6hhn8\":[\"Préférences\"],\"QF4a34\":[\"Veuillez saisir un nom d'utilisateur\"],\"QGqSZ2\":[\"Couleur et mise en forme\"],\"QJQd1J\":[\"Modifier le profil\"],\"QSzGDE\":[\"Inactif\"],\"QUlny5\":[\"Bienvenue sur \",[\"0\"],\" !\"],\"Qoq+GP\":[\"Lire la suite\"],\"QuSkCF\":[\"Filtrer les canaux...\"],\"QwUrDZ\":[\"a changé le sujet en : \",[\"topic\"]],\"R0UH07\":[\"Image \",[\"0\"],\" sur \",[\"1\"]],\"R7SsBE\":[\"Couper le son\"],\"R8rf1X\":[\"Cliquez pour définir le sujet\"],\"RArB3D\":[\"a été expulsé de \",[\"channelName\"],\" par \",[\"username\"]],\"RI3cWd\":[\"Découvrez le monde de l'IRC avec ObsidianIRC\"],\"RIfHS5\":[\"Créer un nouveau lien d'invitation\"],\"RMMaN5\":[\"Modéré (+m)\"],\"RWw9Lg\":[\"Fermer la fenêtre\"],\"RZ2BuZ\":[\"L'enregistrement du compte \",[\"account\"],\" nécessite une vérification : \",[\"message\"]],\"RySp6q\":[\"Masquer les commentaires\"],\"S5Togi\":[\"Chargement des réseaux de votre bouncer…\"],\"SPKQTd\":[\"Le pseudo est requis\"],\"SPVjfj\":[\"Par défaut « aucune raison » si laissé vide\"],\"SQKPvQ\":[\"Inviter un utilisateur\"],\"STmlpb\":[\"Retour à la liste des réseaux\"],\"SkZcl+\":[\"Choisissez un profil de protection contre le flood prédéfini. Ces profils offrent des paramètres de protection équilibrés pour différents cas d'usage.\"],\"Slr+3C\":[\"Utilisateurs min.\"],\"Spnlre\":[\"Vous avez invité \",[\"target\"],\" à rejoindre \",[\"channel\"]],\"T/ckN5\":[\"Ouvrir dans le visualiseur\"],\"T91vKp\":[\"Lire\"],\"TV2Wdu\":[\"Découvrez comment nous gérons vos données et protégeons votre vie privée.\"],\"TgFpwD\":[\"Application en cours...\"],\"TkzSFB\":[\"Aucune modification\"],\"TtserG\":[\"Saisir le vrai nom\"],\"Ttz9J1\":[\"Entrez le mot de passe...\"],\"Tz0i8g\":[\"Paramètres\"],\"U3pytU\":[\"Admin\"],\"UDb2YD\":[\"Réagir\"],\"UE4KO5\":[\"*channel*\"],\"UETAwW\":[\"Vous n'avez encore créé aucun lien d'invitation. Utilisez le formulaire ci-dessus pour créer le premier.\"],\"UGT5vp\":[\"Enregistrer les paramètres\"],\"UV5hLB\":[\"Aucun bannissement trouvé\"],\"Uaj3Nd\":[\"Messages de statut\"],\"Ue3uny\":[\"Par défaut (aucun profil)\"],\"UkARhe\":[\"Normal – Protection standard\"],\"Umn7Cj\":[\"Pas encore de commentaires. Soyez le premier !\"],\"UtUIRh\":[[\"0\"],\" anciens messages\"],\"UwzP+U\":[\"Connexion sécurisée\"],\"V0/A4O\":[\"Propriétaire du canal\"],\"V4qgxE\":[\"Créé avant (min)\"],\"V8yTm6\":[\"Effacer la recherche\"],\"VJMMyz\":[\"ObsidianIRC - L'IRC vers le futur\"],\"VJScHU\":[\"Raison\"],\"VLsmVV\":[\"Couper les notifications\"],\"VbyRUy\":[\"Commentaires\"],\"Vmx0mQ\":[\"Défini par :\"],\"VqnIZz\":[\"Consulter notre politique de confidentialité et nos pratiques en matière de données\"],\"VrMygG\":[\"La longueur minimale est \",[\"0\"]],\"VrnTui\":[\"Vos pronoms, affichés dans votre profil\"],\"W8E3qn\":[\"Compte authentifié\"],\"WAakm9\":[\"Supprimer le canal\"],\"WFxTHC\":[\"Ajouter un masque de bannissement (ex. nick!*@*, *!*@host.com)\"],\"WN1g9F\":[\"L'hôte du serveur est requis\"],\"WRYdXW\":[\"Position audio\"],\"WUOH5B\":[\"Ignorer l'utilisateur\"],\"WWEXnZ\":[[\"0\",\"plural\",{\"one\":[\"Afficher 1 élément de plus\"],\"other\":[\"Afficher \",[\"1\"],\" éléments de plus\"]}]],\"WYxRzo\":[\"Créer et gérer vos liens d'invitation\"],\"Wd38W1\":[\"Laissez le canal vide pour une invitation générique au réseau. La description sert uniquement Ã\xA0 vos notes — visible uniquement par vous dans cette liste.\"],\"Weq9zb\":[\"Général\"],\"Wfj7Sk\":[\"Activer ou désactiver les sons de notification\"],\"Wm7gbG\":[\"GitHub :\"],\"WyeHWY\":[\"*spam*\"],\"WzMCru\":[\"Profil de l'utilisateur\"],\"X6S3lt\":[\"Rechercher des paramètres, canaux, serveurs...\"],\"XEHan5\":[\"Continuer quand même\"],\"XI1+wb\":[\"Format invalide\"],\"XIXeuC\":[\"Message à @\",[\"0\"]],\"XMS+k4\":[\"Démarrer un message privé\"],\"XWgxXq\":[\"Album\"],\"Xd7+IT\":[\"Désépingler la conversation privée\"],\"Xm/s+u\":[\"Affichage\"],\"Xp2n93\":[\"Affiche les médias provenant de l'hébergeur de fichiers de confiance de votre serveur. Aucune requête n'est envoyée à des services externes.\"],\"XvjC4F\":[\"Enregistrement...\"],\"Y/qryO\":[\"Aucun utilisateur ne correspond à votre recherche\"],\"YAqRpI\":[\"Enregistrement du compte réussi pour \",[\"account\"],\" : \",[\"message\"]],\"YEfzvP\":[\"Sujet protégé (+t)\"],\"YQOn6a\":[\"Réduire la liste des membres\"],\"YRCoE9\":[\"Opérateur du canal\"],\"YURQaF\":[\"Voir le profil\"],\"YdBSvr\":[\"Contrôler l'affichage des médias et du contenu externe\"],\"Yj6U3V\":[\"Pas de serveur central :\"],\"YjvpGx\":[\"Pronoms\"],\"YqH4l4\":[\"Aucune clé\"],\"YyUPpV\":[\"Compte :\"],\"ZJSWfw\":[\"Message affiché lors de la déconnexion du serveur\"],\"ZR1dJ4\":[\"Invitations\"],\"ZdWg0V\":[\"Ouvrir dans le navigateur\"],\"ZhRBbl\":[\"Rechercher des messages…\"],\"Zmcu3y\":[\"Filtres avancés\"],\"a2/8e5\":[\"Sujet défini après (min)\"],\"aHKcKc\":[\"Page précédente\"],\"aJTbXX\":[\"Mot de passe Oper\"],\"aQryQv\":[\"Le modèle existe déjà\"],\"aW9pLN\":[\"Nombre maximum d'utilisateurs autorisés. Laissez vide pour aucune limite.\"],\"ah4fmZ\":[\"Affiche également des aperçus de YouTube, Vimeo, SoundCloud et autres services connus.\"],\"aifXak\":[\"Aucun média dans ce salon\"],\"ap2zBz\":[\"Détendu\"],\"az8lvo\":[\"Désactivé\"],\"azXSNo\":[\"Développer la liste des membres\"],\"azdliB\":[\"Se connecter à un compte\"],\"b26wlF\":[\"elle/la\"],\"bD/+Ei\":[\"Strict\"],\"bQ6BJn\":[\"Configurez des règles détaillées de protection contre le flood. Chaque règle précise le type d'activité à surveiller et l'action à prendre lorsque les seuils sont dépassés.\"],\"beV7+y\":[\"L'utilisateur recevra une invitation à rejoindre \",[\"channelName\"],\".\"],\"bk84cH\":[\"Message d'absence\"],\"bkHdLj\":[\"Ajouter un serveur IRC\"],\"bmQLn5\":[\"Ajouter une règle\"],\"bv4cFj\":[\"Transport\"],\"bwRvnp\":[\"Action\"],\"c8+EVZ\":[\"Compte vérifié\"],\"cGYUlD\":[\"Aucun aperçu de média n'est chargé.\"],\"cLF98o\":[\"Afficher les commentaires (\",[\"commentCount\"],\")\"],\"cLKIDO\":[\"Aucun utilisateur disponible\"],\"cSgpoS\":[\"Épingler la conversation privée\"],\"cde3ce\":[\"Message <0>\",[\"0\"],\"</0>\"],\"chQsxg\":[\"Copier la sortie formatée\"],\"cl/A5J\":[\"Bienvenue sur \",[\"__DEFAULT_IRC_SERVER_NAME__\"],\" !\"],\"cnGeoo\":[\"Supprimer\"],\"coPLXT\":[\"Nous ne stockons pas vos communications IRC sur nos serveurs\"],\"crYH/6\":[\"Lecteur SoundCloud\"],\"cv5DQb\":[\"aucun hôte défini\"],\"d3sis4\":[\"Ajouter un serveur\"],\"d9aN5k\":[\"Retirer \",[\"username\"],\" du canal\"],\"dEgA5A\":[\"Annuler\"],\"dGi1We\":[\"Désépingler cette conversation privée\"],\"dJVuyC\":[\"a quitté \",[\"channelName\"],\" (\",[\"reason\"],\")\"],\"dMtLDE\":[\"à\"],\"dXqxlh\":[\"<0>⚠️ Risque de sécurité !</0> Cette connexion peut être vulnérable à l'interception ou aux attaques de type man-in-the-middle.\"],\"da9Q/R\":[\"Modes du canal modifiés\"],\"dhJN3N\":[\"Afficher les commentaires\"],\"dj2xTE\":[\"Ignorer la notification\"],\"dpCzmC\":[\"Paramètres de protection contre le flood\"],\"e9dQpT\":[\"Voulez-vous ouvrir ce lien dans un nouvel onglet ?\"],\"ePK91l\":[\"Modifier\"],\"eYBDuB\":[\"Téléverser une image ou fournir une URL avec substitution optionnelle \",[\"size\"]],\"edBbee\":[\"Bannir \",[\"username\"],\" par hostmask (l'empêche de rejoindre depuis la même adresse IP/hôte)\"],\"ekfzWq\":[\"Paramètres utilisateur\"],\"elPDWs\":[\"Personnalisez votre expérience du client IRC\"],\"eu2osY\":[\"<0>💡 Recommandation :</0> Ne continuez que si vous faites confiance à ce serveur et que vous comprenez les risques. Évitez de partager des informations sensibles ou des mots de passe via cette connexion.\"],\"euEhbr\":[\"Cliquez pour rejoindre \",[\"channel\"]],\"ez3vLd\":[\"Activer la saisie multiligne\"],\"f0J5Ki\":[\"Les communications entre serveurs peuvent utiliser des connexions non chiffrées\"],\"f9BHJk\":[\"Avertir l'utilisateur\"],\"fDOLLd\":[\"Aucun canal trouvé.\"],\"ffzDkB\":[\"Analyses anonymes :\"],\"fq1GF9\":[\"Afficher quand des utilisateurs se déconnectent du serveur\"],\"gEF57C\":[\"Ce serveur ne prend en charge qu'un seul type de connexion\"],\"gJuLUI\":[\"Liste d'ignorés\"],\"gNzMrk\":[\"Avatar actuel\"],\"gjPWyO\":[\"Entrez votre pseudo...\"],\"gz6UQ3\":[\"Agrandir\"],\"h6/IMX\":[\"Ajoutez votre premier réseau\"],\"h6razj\":[\"Exclure le masque de nom de salon\"],\"hG6jnw\":[\"Aucun sujet défini\"],\"hG89Ed\":[\"Image\"],\"hYgDIe\":[\"Créer\"],\"hZ6znB\":[\"Port\"],\"ha+Bz5\":[\"ex. : 100:1440\"],\"he3ygx\":[\"Copier\"],\"hehnjM\":[\"Quantité\"],\"hzdLuQ\":[\"Seuls les utilisateurs avec voice ou plus peuvent parler\"],\"i0qMbr\":[\"Accueil\"],\"iDNBZe\":[\"Notifications\"],\"iH8pgl\":[\"Retour\"],\"iL9SZg\":[\"Bannir l'utilisateur (par pseudo)\"],\"iNt+3c\":[\"Retour à l'image\"],\"iQvi+a\":[\"Ne plus m'avertir de la faible sécurité des liens pour ce serveur\"],\"iSLIjg\":[\"Connecter\"],\"iWXkHH\":[\"Halfop\"],\"iZeTtp\":[\"Hôte du serveur\"],\"idD8Ev\":[\"Enregistré\"],\"iivqkW\":[\"Connecté depuis\"],\"ij+Elv\":[\"Aperçu de l'image\"],\"ilIWp7\":[\"Activer/désactiver les notifications\"],\"iuaqvB\":[\"Utilisez * comme joker. Exemples : baduser!*@*, *!*@spammer.com, troll*!*@*\"],\"ixkTse\":[\"Bot\"],\"j2DGR0\":[\"Bannir par masque d'hôte\"],\"jA4uoI\":[\"Sujet :\"],\"jLXxGK\":[\"https://example.com\"],\"jPSk57\":[\"Raison (facultatif)\"],\"jUV7CU\":[\"Téléverser un avatar\"],\"jW5Uwh\":[\"Contrôle la quantité de médias externes chargés. Désactivé / Sûr / Sources fiables / Tout le contenu.\"],\"jXzms5\":[\"Options de pièce jointe\"],\"jZlrte\":[\"Couleur\"],\"jfC/xh\":[\"Contact\"],\"jywMpv\":[\"#new-channel-name\"],\"k112DD\":[\"Charger les anciens messages\"],\"k3ID0F\":[\"Filtrer les membres…\"],\"k65gsE\":[\"Analyse approfondie\"],\"k7Zgob\":[\"Annuler la connexion\"],\"kAVx5h\":[\"Aucune invitation trouvée\"],\"kCLEPU\":[\"Connecté à\"],\"kF5LKb\":[\"Modèles ignorés :\"],\"kGeOx/\":[\"Rejoindre \",[\"0\"]],\"kITKr8\":[\"Chargement des modes du salon...\"],\"kPpPsw\":[\"Vous êtes un IRC Operator\"],\"kWJmRL\":[\"Vous\"],\"kfcRb0\":[\"Avatar\"],\"kjMqSj\":[\"Copier JSON\"],\"krViRy\":[\"Cliquer pour copier en JSON\"],\"ks71ra\":[\"Exceptions\"],\"kw4lRv\":[\"Semi-opérateur du canal\"],\"kxgIRq\":[\"Sélectionnez ou ajoutez un canal pour commencer.\"],\"ky6dWe\":[\"Aperçu de l'avatar\"],\"l+GxCv\":[\"Chargement des canaux...\"],\"l+IUVW\":[\"Vérification du compte réussie pour \",[\"account\"],\" : \",[\"message\"]],\"l/siQz\":[[\"reconnectCount\",\"plural\",{\"one\":[\"s'est reconnecté\"],\"other\":[\"s'est reconnecté \",[\"reconnectCount\"],\" fois\"]}]],\"l1l8sj\":[\"il y a \",[\"0\"],\" j\"],\"l5NhnV\":[\"#canal (optionnel)\"],\"l5jmzx\":[[\"0\"],\" et \",[\"1\"],\" sont en train d'écrire...\"],\"lCF0wC\":[\"Actualiser\"],\"lHy8N5\":[\"Chargement de canaux supplémentaires...\"],\"lasgrr\":[\"utilisé\"],\"lbpf14\":[\"Rejoindre \",[\"value\"]],\"lfFsZ4\":[\"Canaux\"],\"lkNdiH\":[\"Nom de compte\"],\"ln500L\":[\"ObsidianIRC\"],\"lnCMdg\":[\"Téléverser une image\"],\"loQxaJ\":[\"Je suis de retour\"],\"lvfaxv\":[\"ACCUEIL\"],\"m0oxpP\":[\"Libera Chat\"],\"m16xKo\":[\"Ajouter\"],\"m8flAk\":[\"Aperçu (pas encore envoyé)\"],\"mEPxTp\":[\"<0>⚠️ Attention !</0> N'ouvrez que des liens provenant de sources fiables. Des liens malveillants peuvent compromettre votre sécurité ou votre vie privée.\"],\"mHGdhG\":[\"Informations sur le serveur\"],\"mHS8lb\":[\"Message dans #\",[\"0\"]],\"mIibu3\":[\"Pour les endpoints WebSocket non standards (par exemple soju derrière un reverse proxy), entrez l'URL complète incluant le chemin : <0>wss://host:port/socket</0>.\"],\"mMYBD9\":[\"Large – Portée de protection étendue\"],\"mTGsPd\":[\"Sujet du salon\"],\"mU8j6O\":[\"Pas de messages externes (+n)\"],\"mZp8FL\":[\"Retour automatique à une seule ligne\"],\"mdQu8G\":[\"VotrePseudo\"],\"miSSBQ\":[\"Commentaires (\",[\"commentCount\"],\")\"],\"mvyLSy\":[\"L'utilisateur est authentifié\"],\"mwtcGl\":[\"Fermer les commentaires\"],\"myL0MR\":[\"Supprimer ce réseau ?\"],\"mzI/c+\":[\"Télécharger\"],\"n3fGRk\":[\"défini par \",[\"0\"]],\"nE9jsU\":[\"Détendu – Protection moins agressive\"],\"nNflMD\":[\"Quitter le canal\"],\"nPXkBi\":[\"Chargement des données WHOIS...\"],\"nQnxxF\":[\"Message dans #\",[\"0\"],\" (Maj+Entrée pour nouvelle ligne)\"],\"nWMRxa\":[\"Désépingler\"],\"nkC032\":[\"Aucun profil anti-flood\"],\"o69z4d\":[\"Envoyer un message d'avertissement à \",[\"username\"]],\"o9ylQi\":[\"Recherchez des GIFs pour commencer\"],\"oFGkER\":[\"Avis du serveur\"],\"oOi11l\":[\"Défiler vers le bas\"],\"oPYIL5\":[\"réseau\"],\"oQEzQR\":[\"Nouveau message privé\"],\"oXOSPE\":[\"En ligne\"],\"oal760\":[\"Des attaques man-in-the-middle sur les liens serveur sont possibles\"],\"oeqmmJ\":[\"Sources de confiance\"],\"optX0N\":[\"il y a \",[\"0\"],\" h\"],\"ovBPCi\":[\"Par défaut\"],\"p0Z69r\":[\"Le modèle ne peut pas être vide\"],\"p1KgtK\":[\"Échec du chargement audio\"],\"p59pEv\":[\"Détails supplémentaires\"],\"p7sRI6\":[\"Informer les autres que vous écrivez\"],\"pBm1od\":[\"Canal secret\"],\"pNmiXx\":[\"Votre pseudo par défaut pour tous les serveurs\"],\"pUUo9G\":[\"Nom d'hôte :\"],\"pVGPmz\":[\"Mot de passe du compte\"],\"peNE68\":[\"Permanent\"],\"plhHQt\":[\"Aucune donnée\"],\"pm6+q5\":[\"Avertissement de sécurité\"],\"pn5qSs\":[\"Informations supplémentaires\"],\"q0cR4S\":[\"est maintenant connu sous le nom de **\",[\"newNick\"],\"**\"],\"qFcunY\":[\"Le salon n'apparaîtra pas dans les commandes LIST ou NAMES\"],\"qLpTm/\":[\"Supprimer la réaction \",[\"emoji\"]],\"qVkGWK\":[\"Épingler\"],\"qY8wNa\":[\"Page d'accueil\"],\"qb0xJ7\":[\"Jokers : * correspond à toute séquence, ? à un seul caractère. Exemples : nick!*@*, *!*@host.com, *!*user@*\"],\"qhzpRq\":[\"Clé du salon (+k)\"],\"qtoOYG\":[\"Aucune limite\"],\"r1W2AS\":[\"Image hébergée\"],\"rIPR2O\":[\"Sujet défini avant (min)\"],\"rMMSYo\":[\"La longueur maximale est \",[\"0\"]],\"rWtzQe\":[\"Le réseau s'est divisé et reconnecté. ✅\"],\"rYG2u6\":[\"Veuillez patienter...\"],\"rdUucN\":[\"Aperçu\"],\"rjGI/Q\":[\"Confidentialité\"],\"rk8iDX\":[\"Chargement des GIFs...\"],\"rn6SBY\":[\"Rétablir le son\"],\"s/UKqq\":[\"A été expulsé du canal\"],\"s8cATI\":[\"a rejoint \",[\"channelName\"]],\"sCO9ue\":[\"La connexion à <0>\",[\"serverName\"],\"</0> présente les problèmes de sécurité suivants :\"],\"sGH11W\":[\"Serveur\"],\"sHI1H+\":[\"est maintenant connu sous le nom de **\",[\"newNick\"],\"**\"],\"sJyV04\":[[\"inviter\"],\" vous a invité à rejoindre \",[\"channel\"]],\"sUBSbK\":[\"Aucun réseau en amont pour le moment.\"],\"sby+1/\":[\"Cliquer pour copier\"],\"sfN25C\":[\"Votre nom réel ou complet\"],\"sliuzR\":[\"Ouvrir le lien\"],\"sqrO9R\":[\"Mentions personnalisées\"],\"sr6RdJ\":[\"Multiligne avec Shift+Entrée\"],\"swrCpB\":[\"Le canal a été renommé de \",[\"oldName\"],\" en \",[\"newName\"],\" par \",[\"user\"],[\"0\"]],\"sxkWRg\":[\"Avancé\"],\"t/YqKh\":[\"Supprimer\"],\"t47eHD\":[\"Votre identifiant unique sur ce serveur\"],\"tAkAh0\":[\"URL avec substitution optionnelle \",[\"size\"],\". Exemple : https://example.com/avatar/\",[\"size\"],\"/channel.jpg\"],\"tXLJS3\":[\"Afficher ou masquer la barre latérale de la liste des canaux\"],\"tfDRzk\":[\"Enregistrer\"],\"tiBsJk\":[\"a quitté \",[\"channelName\"]],\"tt4/UD\":[\"a quitté (\",[\"reason\"],\")\"],\"u0TcnO\":[\"Le pseudo {nick} est déjà utilisé, nouvel essai avec {newNick}\"],\"u0a8B4\":[\"S'authentifier en tant qu'opérateur IRC pour l'accès administratif\"],\"u0rWFU\":[\"Créé après (min)\"],\"u72w3t\":[\"Utilisateurs et modèles à ignorer\"],\"u7jc2L\":[\"a quitté\"],\"uAQUqI\":[\"Statut\"],\"uB85T3\":[\"Échec de l'enregistrement : \",[\"msg\"]],\"uV3DOL\":[\"G-Line\"],\"uW3lLI\":[\"Serveurs IRC :\"],\"ukyW4o\":[\"Vos liens d'invitation\"],\"usSSr/\":[\"Niveau de zoom\"],\"v7uvcf\":[\"Logiciel :\"],\"vE8kb+\":[\"Shift+Entrée pour les nouvelles lignes (Entrée envoie)\"],\"vERlcd\":[\"Profil\"],\"vK0RL8\":[\"Pas de sujet\"],\"vSJd18\":[\"Vidéo\"],\"vXIe7J\":[\"Langue\"],\"vaHYxN\":[\"Vrai nom\"],\"vhjbKr\":[\"Absent\"],\"w/nogd\":[[\"0\"],\" réseau\",[\"1\"],\" — choisissez-en un à rejoindre\"],\"w4NYox\":[\"client \",[\"title\"]],\"w8xQRx\":[\"Valeur invalide\"],\"wFjjxZ\":[\"a été expulsé de \",[\"channelName\"],\" par \",[\"username\"],\" (\",[\"reason\"],\")\"],\"wGjaGl\":[\"Aucune exception de bannissement trouvée\"],\"wPrGnM\":[\"Administrateur du canal\"],\"wRkP2d\":[\"GIF\"],\"wbm86v\":[\"Afficher quand des utilisateurs rejoignent ou quittent des salons\"],\"whqZ9r\":[\"Mots ou phrases supplémentaires à surligner\"],\"wm7RV4\":[\"Son de notification\"],\"wz/Yoq\":[\"Vos messages pourraient être interceptés lors du relais entre serveurs\"],\"x3+y8b\":[\"Nombre de personnes inscrites via ce lien\"],\"xCJdfg\":[\"Effacer\"],\"xOTzt5\":[\"Ã\xA0 l'instant\"],\"xUHRTR\":[\"S'authentifier automatiquement comme opérateur à la connexion\"],\"xWHwwQ\":[\"Bannissements\"],\"xYilR2\":[\"Médias\"],\"xbi8D6\":[\"Ce serveur ne prend pas en charge les liens d'invitation (la capacité<0>obby.world/invitation</0>n'est pas annoncée). Vous pouvez toujours discuter normalement ; ce panneau est destiné aux réseaux propulsés par obbyircd.\"],\"xceQrO\":[\"Seuls les websockets sécurisés sont pris en charge\"],\"xdtXa+\":[\"nom-du-salon\"],\"xfXC7q\":[\"Salons textuels\"],\"xlCYOE\":[\"Chargement des messages...\"],\"xlhswE\":[\"La valeur minimale est \",[\"0\"]],\"xq97Ci\":[\"Ajouter un mot ou une expression...\"],\"xuRqRq\":[\"Limite de clients (+l)\"],\"xwF+7J\":[[\"0\"],\" est en train d'écrire...\"],\"y1eoq1\":[\"Copier le lien\"],\"yJztBY\":[\"Supprimer le réseau\"],\"yNeucF\":[\"Ce serveur ne supporte pas les métadonnées de profil étendues (extension IRCv3 METADATA). Les champs comme l'avatar, le nom d'affichage et le statut ne sont pas disponibles.\"],\"yPlrca\":[\"Avatar du salon\"],\"yQE2r9\":[\"Chargement\"],\"ySU+JY\":[\"votre@email.com\"],\"yTX1Rt\":[\"Nom d'utilisateur opérateur\"],\"yYOzWD\":[\"journaux\"],\"yfx9Re\":[\"Mot de passe opérateur IRC\"],\"ygCKqB\":[\"Arrêter\"],\"ymDxJx\":[\"Nom d'utilisateur opérateur IRC\"],\"yrpRsQ\":[\"Trier par nom\"],\"yz7wBu\":[\"Fermer\"],\"zJw+jA\":[\"définit le mode : \",[\"0\"]],\"zbymaY\":[\"il y a \",[\"0\"],\" min\"],\"zebeLu\":[\"Saisir le nom d'utilisateur oper\"],\"zpr0Bw\":[\"GZ-Line\"]}"); No newline at end of file
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Regenerated French bundle still ships mojibake text.

Line 1 contains multiple corrupted strings (Ã, â�) in the serialized catalog; these will render as broken French in production UI. Please regenerate this bundle from a clean UTF-8 .po source and verify the export step preserves UTF-8 end-to-end.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/locales/fr/messages.mjs` at line 1, The exported const messages uses a
serialized JSON string with mojibake (e.g. corrupted sequences like à and â�) —
regenerate the French translation bundle from the original UTF-8 .po source and
replace the JSON.parse(...) payload so all strings are valid UTF-8; ensure the
export step and any tooling (the code that produces the JSON string in messages,
plus the build/save step) write the file with UTF-8 encoding (no CP1252/Latin-1
conversion or BOM) and verify by opening the generated messages string (the
argument to JSON.parse in the exported messages constant) to confirm accented
characters (é, ç, à, ô, etc.) are intact.

…cer's say-so

Mupuf's #120 complaint + your follow-up: when a bouncer network shows
state=connected on the bouncer side, the client should bind to it
automatically -- both incrementally (admin enables a network mid-
session) and on cold start (page reload restores the previously
active set).

New architecture in store/handlers/bouncer.ts
   -----------------------------------------
   autoBindConnectedNetworks(parentId) iterates the bouncer's current
   networks, filters for attributes.state === "connected", computes
   the deterministic uuid-v5 childId, and skips any binding whose
   Server row already exists. The remainder go through
   bouncerConnectNetwork (idempotent).
   Triggers:
     - BOUNCER_NETWORK with `state` present (incremental notify mode)
     - BATCH_END for a soju.im/bouncer-networks batch (initial dump
       sweep, also catches notify-mode flushes that buffered multiple
       updates)

connectToSavedServers no longer pre-dispatches saved children
   ------------------------------------------------------------
   The old design seeded child Server rows from localStorage and
   raced their SASL/BIND against the parent's not-yet-authenticated
   socket -- exactly the "Authentication required" loop you and
   mupuf hit on reload. The new flow boots only parents; children
   come back via the bouncer reducer's auto-bind once the parent
   has responded to LISTNETWORKS. Saved-child entries in localStorage
   become a no-op (left in place for any future restore-on-disconnect
   path, but not acted on here).

Tests rewritten to match the new contract:
  - connectToSavedServers.test.ts: pins parent-only dispatch and
    explicitly asserts NO setPendingBouncerBind / child connect
    after a parent's `ready` event.
  - bouncerAutoBind.test.ts: covers the per-event auto-bind --
    state=connected triggers a wire bind, state=disconnected
    doesn't. A third case for the BATCH_END sweep was attempted
    but vi.spyOn() interacts poorly with zustand's state-replacing
    setState across multiple triggers in one test (chain executes
    correctly per debug logs but the spy stops recording after the
    first call). The implementation is the same code path -- left
    to manual verification against a real soju.

882 tests + build green.
…plicate-server explosion)

The previous dedup checked state.servers.some(s => s.id === childId) on
every call, but the per-event trigger fires once per BOUNCER_NETWORK,
and during the initial LISTNETWORKS dump those events arrive back-to-
back in the same tick. Each invocation's state snapshot in some flows
saw the pre-set version even though bouncerConnectNetwork's set() is
synchronous on paper -- result was the same network being seeded
multiple times (you reported 12+ duplicate rows for a 3-network
bouncer).

Switch to a module-scope Set of attempted childIds. Cheap, correct,
unaffected by store-snapshot timing. The Set resets on every page load
(fresh module), which is the only time we genuinely want auto-bind to
re-fire from scratch.

Tests still green (the existing cases cover single-event triggers; the
multi-event burst was the failing case I had to delete earlier because
of the same vi.spyOn / zustand interaction).
…nnections (fix exponential server explosion)

Soju advertises soju.im/bouncer-networks on bound child connections
too (not just the meta control session) and sends them LISTNETWORKS.
Without a guard, every child reduces those replies through the same
auto-bind handler -- using the CHILD's serverId as the bouncerServerId,
computing fresh uuid-v5 childIds against that, and seeding more
"grandchild" Server rows. Each grandchild then becomes a bouncer in
state and the cycle repeats.

Result was an exponential explosion: 3 networks bound on the parent
produced 3 children, then 9 grandchildren, then 27, etc. -- the 50+
duplicate rows ValwareIRC pasted in console logs.

Guard: when autoBindConnectedNetworks is called for a serverId, look
up the Server row -- if it has `bouncerNetid` set, it's itself a
child; return early. Only true bouncer parents drive the auto-bind.

Also drops the debug console.warn lines now that the diagnosis is in.
…k' header

Three UX polish items on top of the bouncer stack:

* Server-list tab badge -- top-right of every soju-bouncer server tile.
  Shotglass icon (react-icons/gi GiGlassShot) signals "this is on the
  soju bouncer"; paired with a crown for the control session and a
  plug for each bound upstream network. Tooltip surfaces the same.
  Mention dot was moved to the bottom-right corner so it doesn't
  overlap the new badge.

* Edit pencil for a bouncer-bound child no longer opens the generic
  AddServerModal -- most ServerConfig fields are irrelevant for those
  rows (host/port/sasl etc. are inherited from the parent). It now
  selects the parent and switches the BouncerNetworksPanel straight
  into its inline edit form for the right netid. Plumbed via a
  ui.pendingBouncerEdit hand-off slot + matching editBouncerNetwork
  / consumePendingBouncerEdit store actions.

* ChatHeader's blank-page CTA now reads "Select a Network" when the
  selected server is the soju control session (no real channels of
  its own, the panel is for picking an upstream network). Channels
  on bound children still get "Select a channel".

3 new strings translated across all 18 non-English locales; 883
tests + build green.
…r-networks CAP ack

The shotglass+crown sidebar badge keyed on Server.isBouncerControl,
which was previously only set on entries restored from saved storage
(seed of the children Server rows in connectToSavedServers). Fresh
AddServerModal connections never picked up the flag, so the parent
tab stayed unbadged even though everything else worked.

When the CAP ack handler sets bouncers[serverId].supported = true,
also flip the matching Server row to isBouncerControl = true -- but
only when the Server isn't itself a bouncer-bound child (which would
mean the same cap is being negotiated on a child connection, and the
crown belongs to the parent, not the child).
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/locales/cs/messages.po (1)

59-63: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use ICU pluralization instead of the English {1} suffix.

At Line 63, {0} sítí{1} can render impossible Czech like 2 sítís. This needs a real plural message for Czech counts instead of carrying the English plural suffix through the translation.

Suggested translation shape
-msgstr "{0} sítí{1} — vyberte jednu pro připojení"
+msgstr "{0, plural, one {{0} síť — vyberte ji pro připojení} few {{0} sítě — vyberte jednu pro připojení} other {{0} sítí — vyberte jednu pro připojení}}"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/locales/cs/messages.po` around lines 59 - 63, The translated string for
msgid "{0} network{1} — pick one to join" in src/locales/cs/messages.po must not
reuse the English "{1}" suffix; replace it with ICU pluralization: use the {0}
count and an ICU plural expression (e.g. {0, plural, one {...} few {...} other
{...}}) in msgstr so Czech singular/few/other forms are correct and remove the
{1} placeholder; update the translation for the Czech forms accordingly and
ensure the code that formats this message (referenced from
BouncerNetworksPanel.tsx) passes the numeric count as placeholder {0}.
♻️ Duplicate comments (1)
src/locales/ja/messages.po (1)

85-99: ⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

Mojibake corruption persists across multiple Japanese translations.

The previously flagged UTF-8 encoding corruption remains unfixed. Many msgstr entries display garbled byte sequences instead of valid Japanese characters (e.g., line 88: "{0}æ¥å" should be "{0}日前"; line 599: "ããä¸åº¦ã¯ãªãã¯ãã¦ç¢ºèª" should be "もう一度クリックして確認"). This will render broken UI copy across invitations, time-ago labels, and other user-facing strings.

Restore valid UTF-8 Japanese translations by replacing corrupted msgstr values with correctly encoded text, and ensure the file is saved and committed as UTF-8 without double-encoding.

Also applies to: 132-135, 597-600, 699-702, 745-752, 765-776, 858-873, 933-936, 1334-1374, 1620-1623, 1740-1751, 2112-2116, 2544-2555, 2710-2713, 2908-2935

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/locales/ja/messages.po` around lines 85 - 99, Multiple Japanese msgstr
entries are mojibake-corrupted (e.g., msgid "{0}d ago", "{0}h ago", "{0}m ago"
and others); open src/locales/ja/messages.po, locate the affected msgid keys
(like "{0}d ago", "{0}h ago", "{0}m ago", "Click again to confirm" equivalents
shown in the review) and replace their garbled msgstr values with properly
encoded Japanese text (e.g., "{0}日前", "{0}時間前", "{0}分前", "もう一度クリックして確認" etc.),
then save and commit the .po file encoded as UTF-8 (ensure no double-encoding or
BOM) so the translations render correctly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/locales/en/messages.po`:
- Around line 1627-1629: The locale catalog src/locales/en/messages.po is out of
sync (see msgid "Network bound through soju bouncer" used in
src/components/layout/ServerList.tsx); regenerate the i18n catalogs by running
the project's extraction/compile commands (e.g., i18n extract/compile or the
project's npm/yarn scripts that update src/locales/), verify messages.po and
other generated files are updated, and commit the regenerated files so the CI
i18n catalog check passes.

In `@src/locales/ja/messages.mjs`:
- Line 1: The messages export contains widespread mojibake due to an
encoding/serialization issue; regenerate the Japanese catalog from a clean UTF-8
source and replace the current JSON payload in the messages constant so all
strings are valid Japanese (no sequences like "ã..." or broken escapes). Rebuild
the localization generation/compile step that produces the messages variable and
ensure the pipeline writes the output file using UTF-8 (verify JSON.stringify
output or your i18n tool emits proper Unicode and the writeFile/writeStream uses
'utf8'), then run the same JSON.parse(...) usage as before to confirm the file
loads correctly at runtime.

---

Outside diff comments:
In `@src/locales/cs/messages.po`:
- Around line 59-63: The translated string for msgid "{0} network{1} — pick one
to join" in src/locales/cs/messages.po must not reuse the English "{1}" suffix;
replace it with ICU pluralization: use the {0} count and an ICU plural
expression (e.g. {0, plural, one {...} few {...} other {...}}) in msgstr so
Czech singular/few/other forms are correct and remove the {1} placeholder;
update the translation for the Czech forms accordingly and ensure the code that
formats this message (referenced from BouncerNetworksPanel.tsx) passes the
numeric count as placeholder {0}.

---

Duplicate comments:
In `@src/locales/ja/messages.po`:
- Around line 85-99: Multiple Japanese msgstr entries are mojibake-corrupted
(e.g., msgid "{0}d ago", "{0}h ago", "{0}m ago" and others); open
src/locales/ja/messages.po, locate the affected msgid keys (like "{0}d ago",
"{0}h ago", "{0}m ago", "Click again to confirm" equivalents shown in the
review) and replace their garbled msgstr values with properly encoded Japanese
text (e.g., "{0}日前", "{0}時間前", "{0}分前", "もう一度クリックして確認" etc.), then save and
commit the .po file encoded as UTF-8 (ensure no double-encoding or BOM) so the
translations render correctly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c81f2dd5-e780-4124-8961-1c90a2f1583e

📥 Commits

Reviewing files that changed from the base of the PR and between 456d998 and b09f99b.

📒 Files selected for processing (45)
  • src/components/layout/ChatHeader.tsx
  • src/components/layout/ServerList.tsx
  • src/components/ui/BouncerNetworksPanel.tsx
  • src/locales/cs/messages.mjs
  • src/locales/cs/messages.po
  • src/locales/de/messages.mjs
  • src/locales/de/messages.po
  • src/locales/en/messages.mjs
  • src/locales/en/messages.po
  • src/locales/es/messages.mjs
  • src/locales/es/messages.po
  • src/locales/fi/messages.mjs
  • src/locales/fi/messages.po
  • src/locales/fr/messages.mjs
  • src/locales/fr/messages.po
  • src/locales/it/messages.mjs
  • src/locales/it/messages.po
  • src/locales/ja/messages.mjs
  • src/locales/ja/messages.po
  • src/locales/ko/messages.mjs
  • src/locales/ko/messages.po
  • src/locales/nl/messages.mjs
  • src/locales/nl/messages.po
  • src/locales/pl/messages.mjs
  • src/locales/pl/messages.po
  • src/locales/pt/messages.mjs
  • src/locales/pt/messages.po
  • src/locales/ro/messages.mjs
  • src/locales/ro/messages.po
  • src/locales/ru/messages.mjs
  • src/locales/ru/messages.po
  • src/locales/sv/messages.mjs
  • src/locales/sv/messages.po
  • src/locales/tr/messages.mjs
  • src/locales/tr/messages.po
  • src/locales/uk/messages.mjs
  • src/locales/uk/messages.po
  • src/locales/zh-TW/messages.mjs
  • src/locales/zh-TW/messages.po
  • src/locales/zh/messages.mjs
  • src/locales/zh/messages.po
  • src/store/handlers/bouncer.ts
  • src/store/index.ts
  • tests/store/bouncerAutoBind.test.ts
  • tests/store/connectToSavedServers.test.ts
✅ Files skipped from review due to trivial changes (3)
  • src/locales/en/messages.mjs
  • src/locales/cs/messages.mjs
  • src/locales/it/messages.mjs
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/locales/fr/messages.mjs
  • src/locales/es/messages.mjs
  • src/locales/de/messages.mjs
  • src/locales/fi/messages.mjs
  • src/components/ui/BouncerNetworksPanel.tsx
  • src/locales/fr/messages.po

Comment thread src/locales/en/messages.po
Comment thread src/locales/ja/messages.mjs Outdated
@@ -1 +1 @@
/*eslint-disable*/export const messages=JSON.parse("{\"+5VMrz\":[\"無効なパターン形式です。nick!user@host 形式を使用してください(ワイルドカード * 使用可)\"],\"+6NQQA\":[\"一般サポートチャンネル\"],\"+6NyRG\":[\"クライアント\"],\"+K0AvT\":[\"切断\"],\"+cyFdH\":[\"離席時に表示するデフォルトメッセージ\"],\"+mVPqU\":[\"メッセージ内のMarkdown書式を表示する\"],\"+vqCJH\":[\"認証用のアカウントユーザー名\"],\"+yPBXI\":[\"ファイルを選択\"],\"+zy2Nq\":[\"種類\"],\"/09cao\":[\"リンクセキュリティが低い(レベル \",[\"securityLevel\"],\")\"],\"/3BQ4J\":[\"チャンネル外のユーザーはメッセージを送信できません\"],\"/4C8U0\":[\"すべてコピー\"],\"/6BzZF\":[\"メンバーリストを切り替え\"],\"/AkXyp\":[\"確認しますか?\"],\"/TNOPk\":[\"ユーザーは離席中です\"],\"/XQgft\":[\"探す\"],\"/cF7Rs\":[\"音量\"],\"/dqduX\":[\"次のページ\"],\"/fc3q4\":[\"すべてのコンテンツ\"],\"/kISDh\":[\"通知音を有効にする\"],\"/n04sB\":[\"Kill\"],\"/rTz0M\":[\"音声\"],\"/rfkZe\":[\"メンションやメッセージに対してサウンドを再生する\"],\"0/0ZGA\":[\"チャンネル名マスク\"],\"0D6j7U\":[\"カスタムルールについて詳しく →\"],\"0XsHcR\":[\"ユーザーをキック\"],\"0ZpE//\":[\"ユーザー数順で並び替え\"],\"0bEPwz\":[\"離席中に設定\"],\"0dGkPt\":[\"チャンネルリストを展開\"],\"0gS7M5\":[\"表示名\"],\"0kS+M8\":[\"サンプルNET\"],\"0rgoY7\":[\"自分で選んだサーバーにのみ接続します\"],\"0wdd7X\":[\"参加\"],\"0wkVYx\":[\"プライベートメッセージ\"],\"111uHX\":[\"リンクプレビュー\"],\"196EG4\":[\"プライベートチャットを削除\"],\"1DSr1i\":[\"アカウントを登録する\"],\"1O/24y\":[\"チャンネルリストを切り替え\"],\"1VPJJ2\":[\"外部リンクの警告\"],\"1ZC/dv\":[\"未読のメンションやメッセージはありません\"],\"1pO1zi\":[\"サーバー名は必須です\"],\"1uwfzQ\":[\"チャンネルトピックを表示\"],\"268g7c\":[\"表示名を入力\"],\"2F9+AZ\":[\"まだ生のIRCトラフィックを取得していません。接続するかメッセージを送信してください。\"],\"2FOFq1\":[\"ネットワーク上のサーバーオペレーターがメッセージを閲覧できる可能性があります\"],\"2FYpfJ\":[\"詳細\"],\"2HF1Y2\":[[\"inviter\"],\" が \",[\"target\"],\" を \",[\"channel\"],\" に招待しました\"],\"2I70QL\":[\"ユーザープロフィール情報を表示する\"],\"2QYdmE\":[\"ユーザー:\"],\"2QpEjG\":[\"退出しました\"],\"2YE223\":[\"#\",[\"0\"],\" へメッセージ(Enterで改行、Shift+Enterで送信)\"],\"2bimFY\":[\"サーバーパスワードを使用する\"],\"2iTmdZ\":[\"ローカルストレージ:\"],\"2odkwe\":[\"厳格 — より積極的な保護\"],\"2uDhbA\":[\"招待するユーザー名を入力\"],\"2ygf/L\":[\"← 戻る\"],\"2zEgxj\":[\"GIFを検索...\"],\"3RdPhl\":[\"チャンネル名を変更\"],\"3THokf\":[\"Voiceユーザー\"],\"3TSz9S\":[\"最小化\"],\"3jBDvM\":[\"チャンネル表示名\"],\"3ryuFU\":[\"アプリ改善のための任意のクラッシュレポート\"],\"3uBF/8\":[\"ビューアを閉じる\"],\"3uwW8F\":[\"https://example.com/avatar/\",[\"size\"],\"/channel.jpg\"],\"3xf8Kz\":[\"アカウント名を入力...\"],\"4/Rr0R\":[\"現在のチャンネルにユーザーを招待する\"],\"4EZrJN\":[\"ルール\"],\"4JJtW9\":[\"#overflow\"],\"4NqeT4\":[\"フラッドプロファイル (+F)\"],\"4RZQRK\":[\"今何してるの?\"],\"4hfTrB\":[\"ニックネーム\"],\"4n99LO\":[\"すでに \",[\"0\"],\" にいます\"],\"4t6vMV\":[\"短いメッセージの場合は自動的に1行入力に切り替える\"],\"4vsHmf\":[\"時間(分)\"],\"5+INAX\":[\"自分へのメンションを含むメッセージをハイライトする\"],\"5R5Pv/\":[\"Oper名\"],\"678PKt\":[\"ネットワーク名\"],\"6Aih4U\":[\"オフライン\"],\"6CO3WE\":[\"チャンネルに参加するために必要なパスワード。キーを削除するには空欄にしてください。\"],\"6HhMs3\":[\"退出メッセージ\"],\"6V3Ea3\":[\"コピーしました\"],\"6lGV3K\":[\"折りたたむ\"],\"6yFOEi\":[\"oper パスワードを入力...\"],\"7+IHTZ\":[\"ファイルが選択されていません\"],\"73hrRi\":[\"nick!user@host(例:spam*!*@*、*!*@badhost.com)\"],\"7QkKyN\":[\"プライベートメッセージを送信\"],\"7U1W7c\":[\"とても緩め\"],\"7Y1YQj\":[\"本名:\"],\"7YHArF\":[\"— ビューアで開く\"],\"7fjnVl\":[\"ユーザーを検索...\"],\"7jL88x\":[\"このメッセージを削除しますか?この操作は元に戻せません。\"],\"7nGhhM\":[\"今どんな気分ですか?\"],\"7sEpu1\":[\"メンバー — \",[\"0\"]],\"7sNhEz\":[\"ユーザー名\"],\"8H0Q+x\":[\"プロファイルについて詳しく →\"],\"8Phu0A\":[\"ユーザーがニックネームを変更したときに表示する\"],\"8XTG9e\":[\"Operパスワードを入力\"],\"8XsV2J\":[\"再送信\"],\"8ZsakT\":[\"パスワード\"],\"8kR84m\":[\"外部リンクを開こうとしています:\"],\"8lCgih\":[\"ルールを削除\"],\"8o3dPc\":[\"ファイルをドロップしてアップロード\"],\"8p/xVT\":[[\"0\",\"plural\",{\"one\":[[\"1\"]],\"other\":[[\"2\"]]}]],\"8wRzac\":[[\"joinCount\",\"plural\",{\"other\":[[\"joinCount\"],\"回 参加した\"]}]],\"9BMLnJ\":[\"サーバーに再接続\"],\"9OEgyT\":[\"リアクションを追加\"],\"9PQ8m2\":[\"G-Line(グローバルBAN)\"],\"9Qs99X\":[\"メール:\"],\"9QupBP\":[\"パターンを削除\"],\"9bG48P\":[\"送信中\"],\"9f5f0u\":[\"プライバシーに関するご質問はこちら:\"],\"9unqs3\":[\"退席中:\"],\"9v3hwv\":[\"サーバーが見つかりません。\"],\"9zb2WA\":[\"接続中\"],\"A1taO8\":[\"検索\"],\"A2adVi\":[\"入力中通知を送信する\"],\"A9Rhec\":[\"チャンネル名\"],\"AWOSPo\":[\"ズームイン\"],\"AXSpEQ\":[\"接続時にOperになる\"],\"AeXO77\":[\"アカウント\"],\"AhNP40\":[\"シーク\"],\"Ai2U7L\":[\"ホスト\"],\"AjBQnf\":[\"ニックネームを変更しました\"],\"AmXVh6\":[\"https://example.com/avatar.png\"],\"AnRu/j\":[\"返信をキャンセル\"],\"ApSx0O\":[\"\\\"\",[\"searchQuery\"],\"\\\"に一致するメッセージが\",[\"0\"],\"件見つかりました\"],\"AxPAXW\":[\"結果が見つかりません\"],\"AyNqAB\":[\"すべてのサーバーイベントをチャットに表示する\"],\"B/QqGw\":[\"席を外しています\"],\"B8AaMI\":[\"この項目は必須です\"],\"BA2c49\":[\"このサーバーは高度なLISTフィルタリングをサポートしていません\"],\"BDKt3I\":[[\"0\"],\"、\",[\"1\"],\"、\",[\"2\"],\" と他 \",[\"3\"],\" 人が入力中...\"],\"BGul2A\":[\"保存されていない変更があります。保存せずに閉じてもよいですか?\"],\"BIf9fi\":[\"ステータスメッセージ\"],\"BPm98R\":[\"サーバーが選択されていません。まずサイドバーからサーバーを選択してください。招待リンクはサーバーごとに管理されます。\"],\"BZz3md\":[\"個人ウェブサイト\"],\"Bgm/H7\":[\"複数行のテキスト入力を許可する\"],\"BiQIl1\":[\"このプライベートメッセージの会話をピン留めする\"],\"BlNZZ2\":[\"クリックしてメッセージに移動\"],\"Bowq3c\":[\"オペレーターのみがチャンネルトピックを変更できます\"],\"Btozzp\":[\"この画像の有効期限が切れています\"],\"Bycfjm\":[\"合計:\",[\"0\"]],\"C6IBQc\":[\"JSON全体をコピー\"],\"C9L9wL\":[\"データ収集\"],\"CDq4wC\":[\"ユーザーをモデレート\"],\"CHVRxG\":[\"@\",[\"0\"],\" へメッセージ(Shift+Enterで改行)\"],\"CN9zdR\":[\"Oper名とパスワードは必須です\"],\"CW3sYa\":[\"リアクション \",[\"emoji\"],\" を追加\"],\"CaAkqd\":[\"退出を表示\"],\"CbvaYj\":[\"ニックネームでBAN\"],\"CcK+Ft\":[\"PDF\"],\"Ce8q3L\":[\"チャンネルを選択\"],\"CsekCi\":[\"通常\"],\"D+NlUC\":[\"システム\"],\"D28t6+\":[\"参加して退出しました\"],\"DB8zMK\":[\"適用\"],\"DBcWHr\":[\"カスタム通知音ファイル\"],\"DTy9Xw\":[\"メディアプレビュー\"],\"Dj4pSr\":[\"安全なパスワードを選択してください\"],\"Du+zn+\":[\"検索中...\"],\"Du2T2f\":[\"設定が見つかりません\"],\"DwsSVQ\":[\"フィルターを適用して更新\"],\"E3W/zd\":[\"デフォルトニックネーム\"],\"E6nRW7\":[\"URLをコピー\"],\"E703RG\":[\"モード:\"],\"EAeu1Z\":[\"招待を送信\"],\"EFKJQT\":[\"設定\"],\"EGPQBv\":[\"カスタムフラッドルール (+f)\"],\"ELik0r\":[\"プライバシーポリシー全文を見る\"],\"EPbeC2\":[\"チャンネルトピックを表示または編集する\"],\"EQCDNT\":[\"oper ユーザー名を入力...\"],\"EUvulZ\":[\"\\\"\",[\"searchQuery\"],\"\\\"に一致するメッセージが1件見つかりました\"],\"EatZYJ\":[\"次の画像\"],\"EdQY6l\":[\"なし\"],\"EnqLYU\":[\"サーバーを検索...\"],\"F0OKMc\":[\"サーバーを編集\"],\"F6Int2\":[\"ハイライトを有効にする\"],\"FDoLyE\":[\"最大ユーザー数\"],\"FUU/hZ\":[\"チャットで読み込む外部メディアの量を制御します。\"],\"Fdp03t\":[\"オン\"],\"FfPWR0\":[\"モーダル\"],\"FjkaiT\":[\"ズームアウト\"],\"FlqOE9\":[\"これが意味すること:\"],\"FolHNl\":[\"アカウントと認証を管理する\"],\"Fp2Dif\":[\"サーバーを退出しました\"],\"G5KmCc\":[\"GZ-Line(グローバルZ-Line)\"],\"GDs0lz\":[\"<0>リスク:</0> 機密情報(メッセージ、プライベート会話、認証情報)が、IRCサーバー間に位置するネットワーク管理者や攻撃者に露出する可能性があります。\"],\"GR+2I3\":[\"招待マスクを追加(例:nick!*@*、*!*@host.com)\"],\"GRLyMU\":[\"ポップアウトしたサーバー通知を閉じる\"],\"GdhD7H\":[\"もう一度クリックして確認\"],\"GlHnXw\":[\"ニックネームの変更に失敗しました: \",[\"error\"],\" \",[\"0\"]],\"GswZF3\":[\"プレビュー:\"],\"GtmO8/\":[\"から\"],\"GtuHUQ\":[\"サーバー上でこのチャンネルの名前を変更します。すべてのユーザーに新しい名前が表示されます。\"],\"GuGfFX\":[\"検索を切り替え\"],\"GxkJXS\":[\"アップロード中...\"],\"GzbwnK\":[\"チャンネルに参加しました\"],\"GzsUDB\":[\"拡張プロフィール\"],\"H/PnT8\":[\"絵文字を挿入\"],\"H6Izzl\":[\"お好みのカラーコード\"],\"H9jIv+\":[\"参加/退出を表示\"],\"HAKBY9\":[\"ファイルをアップロード\"],\"HdE1If\":[\"チャンネル\"],\"Hk4AW9\":[\"お好みの表示名\"],\"HmHDk7\":[\"メンバーを選択\"],\"HrQzPU\":[[\"networkName\"],\" のチャンネル\"],\"I2tXQ5\":[\"@\",[\"0\"],\" へメッセージ(Enterで改行、Shift+Enterで送信)\"],\"I6bw/h\":[\"ユーザーをBAN\"],\"I92Z+b\":[\"通知を有効にする\"],\"I9D72S\":[\"このメッセージを削除してもよいですか?この操作は元に戻せません。\"],\"IA+1wo\":[\"ユーザーがチャンネルからキックされたときに表示する\"],\"IDwkJx\":[\"IRC Operator\"],\"ILlU+s\":[\"情報:\"],\"IUwGEM\":[\"変更を保存\"],\"IVeGK6\":[[\"0\"],\"、\",[\"1\"],\"、\",[\"2\"],\" が入力中...\"],\"IgrLD/\":[\"一時停止\"],\"Im6JED\":[\"WHISPER\"],\"ImOQa9\":[\"返信\"],\"IoHMnl\":[\"最大値は \",[\"0\"],\" です\"],\"IvMj+0\":[\"Op\"],\"J28zul\":[\"接続中...\"],\"J5T9NW\":[\"ユーザー情報\"],\"J8Y5+z\":[\"おっと!ネットワーク分割が発生しました!⚠️\"],\"JBHkBA\":[\"チャンネルを退出しました\"],\"JCwL0Q\":[\"理由を入力(任意)\"],\"JFciKP\":[\"切り替え\"],\"JXGkhG\":[\"チャンネル名を変更する(オペレーターのみ)\"],\"JcD7qf\":[\"その他のアクション\"],\"JdkA+c\":[\"シークレット (+s)\"],\"Jmu12l\":[\"サーバーチャンネル\"],\"JvQ++s\":[\"Markdownを有効にする\"],\"K2jwh/\":[\"WHOISデータがありません\"],\"KAXSwC\":[\"Voice\"],\"KDfTdX\":[\"メッセージを削除\"],\"KKBlUU\":[\"埋め込み\"],\"KM0pLb\":[\"チャンネルへようこそ!\"],\"KR6W2h\":[\"無視を解除\"],\"KV+Bi1\":[\"招待制 (+i)\"],\"KdCtwE\":[\"カウンターをリセットするまでフラッドアクティビティを監視する秒数\"],\"Kkezga\":[\"サーバーパスワード\"],\"KsiQ/8\":[\"ユーザーはチャンネルに参加するために招待が必要です\"],\"L+gB/D\":[\"チャンネル情報\"],\"LC1a7n\":[\"IRCサーバーは、サーバー間リンクのセキュリティレベルが低いと報告しています。これは、ネットワーク内のIRCサーバー間でメッセージが中継される際に、適切に暗号化されていないか、SSL/TLS証明書が正しく検証されない可能性があることを意味します。\"],\"LNfLR5\":[\"キックを表示\"],\"LQb0W/\":[\"すべてのイベントを表示\"],\"LU7/yA\":[\"UI上で表示するための別名です。スペース、絵文字、特殊文字を含めることができます。実際のチャンネル名(\",[\"channelName\"],\")は引き続きIRCコマンドで使用されます。\"],\"LUb9O7\":[\"有効なサーバーポートが必要です\"],\"LV4fT6\":[\"説明(任意、例:「Q3ベータテスター」)\"],\"Lb+BUl\":[\"https://example.com/avatar.jpg\"],\"LcET2C\":[\"プライバシーポリシー\"],\"LcuSDR\":[\"プロフィール情報とメタデータを管理する\"],\"LqLS9B\":[\"ニックネーム変更を表示\"],\"LsDQt2\":[\"チャンネル設定\"],\"LtI9AS\":[\"オーナー\"],\"LuNhhL\":[\"このメッセージにリアクションしました\"],\"M/AZNG\":[\"アバター画像のURL\"],\"M/WIer\":[\"メッセージを送信\"],\"M8er/5\":[\"名前:\"],\"MHk+7g\":[\"前の画像\"],\"MRorGe\":[\"DMを送る\"],\"MVbSGP\":[\"時間ウィンドウ(秒)\"],\"MkpcsT\":[\"メッセージと設定はデバイス上にローカルで保存されます\"],\"N/hDSy\":[\"ボットとしてマーク — 通常は「on」または空欄\"],\"N7TQbE\":[[\"channelName\"],\" にユーザーを招待\"],\"NCca/o\":[\"デフォルトニックネームを入力...\"],\"Nqs6B9\":[\"すべての外部メディアを表示します。URLが不明なサーバーへのリクエストを引き起こす場合があります。\"],\"Nt+9O7\":[\"生のTCPの代わりにWebSocketを使用する\"],\"NxIHzc\":[\"ユーザーを切断\"],\"O+v/cL\":[\"サーバー上のすべてのチャンネルを一覧表示\"],\"ODwSCk\":[\"GIFを送信\"],\"OGQ5kK\":[\"通知音とハイライトを設定する\"],\"OIPt1Z\":[\"メンバーリストのサイドバーを表示/非表示にする\"],\"OKSNq/\":[\"とても厳格\"],\"ONWvwQ\":[\"アップロード\"],\"OVKoQO\":[\"認証用のアカウントパスワード\"],\"ObsidianIRC - Bringing IRC to the future\":[\"ObsidianIRC - IRCを未来へ\"],\"OhCpra\":[\"トピックを設定…\"],\"OkltoQ\":[[\"username\"],\" をニックネームでBANする(同じニックネームでの再参加を防止)\"],\"P+t/Te\":[\"追加データなし\"],\"P42Wcc\":[\"安全\"],\"PD38l0\":[\"チャンネルアバタープレビュー\"],\"PD9mEt\":[\"メッセージを入力...\"],\"PPqfdA\":[\"チャンネル設定を開く\"],\"PSCjfZ\":[\"このチャンネルに表示されるトピックです。すべてのユーザーがトピックを閲覧できます。\"],\"PZCecv\":[\"PDFプレビュー\"],\"PeLgsC\":[[\"c\",\"plural\",{\"other\":[[\"c\"],\"回\"]}]],\"PguS2C\":[\"例外マスクを追加(例:nick!*@*、*!*@host.com)\"],\"Pil5Ty\":[[\"0\"],\" 件中 \",[\"displayedChannelsCount\"],\" 件を表示中\"],\"PqhVlJ\":[\"ユーザーをBAN(ホストマスク)\"],\"Q+chwU\":[\"ユーザー名:\"],\"Q2QY4/\":[\"この招待を削除\"],\"Q6hhn8\":[\"設定\"],\"QF4a34\":[\"ユーザー名を入力してください\"],\"QGqSZ2\":[\"カラーと書式設定\"],\"QJQd1J\":[\"プロフィールを編集\"],\"QSzGDE\":[\"アイドル\"],\"QUlny5\":[[\"0\"],\" へようこそ!\"],\"Qoq+GP\":[\"もっと読む\"],\"QuSkCF\":[\"チャンネルをフィルター...\"],\"QwUrDZ\":[\"トピックを変更しました: \",[\"topic\"]],\"R0UH07\":[[\"1\"],\"枚中\",[\"0\"],\"枚目の画像\"],\"R7SsBE\":[\"ミュート\"],\"R8rf1X\":[\"クリックしてトピックを設定\"],\"RArB3D\":[[\"username\"],\" によって \",[\"channelName\"],\" からキックされました\"],\"RI3cWd\":[\"ObsidianIRCでIRCの世界を探索しよう\"],\"RIfHS5\":[\"新しい招待リンクを作成\"],\"RMMaN5\":[\"モデレート制 (+m)\"],\"RWw9Lg\":[\"モーダルを閉じる\"],\"RZ2BuZ\":[[\"account\"],\" のアカウント登録には確認が必要です: \",[\"message\"]],\"RySp6q\":[\"コメントを非表示\"],\"SPKQTd\":[\"ニックネームは必須です\"],\"SPVjfj\":[\"空欄の場合は「理由なし」がデフォルトになります\"],\"SQKPvQ\":[\"ユーザーを招待\"],\"SkZcl+\":[\"定義済みのフラッド保護プロファイルを選択してください。これらのプロファイルは、さまざまなユースケースに対してバランスの取れた保護設定を提供します。\"],\"Slr+3C\":[\"最小ユーザー数\"],\"Spnlre\":[[\"target\"],\" を \",[\"channel\"],\" に招待しました\"],\"T/ckN5\":[\"ビューアで開く\"],\"T91vKp\":[\"再生\"],\"TV2Wdu\":[\"データの取り扱いとプライバシー保護について詳しく見る。\"],\"TgFpwD\":[\"適用中...\"],\"TkzSFB\":[\"変更なし\"],\"TtserG\":[\"本名を入力\"],\"Ttz9J1\":[\"パスワードを入力...\"],\"Tz0i8g\":[\"設定\"],\"U3pytU\":[\"管理者\"],\"UDb2YD\":[\"リアクション\"],\"UE4KO5\":[\"*channel*\"],\"UETAwW\":[\"まだ招待リンクを作成していません。上のフォームから最初の招待リンクを作成してください。\"],\"UGT5vp\":[\"設定を保存\"],\"UV5hLB\":[\"BANが見つかりません\"],\"Uaj3Nd\":[\"ステータスメッセージ\"],\"Ue3uny\":[\"デフォルト(プロファイルなし)\"],\"UkARhe\":[\"通常 — 標準的な保護\"],\"Umn7Cj\":[\"まだコメントはありません。最初のコメントを投稿しましょう!\"],\"UtUIRh\":[[\"0\"],\" 件の古いメッセージ\"],\"UwzP+U\":[\"セキュア接続\"],\"V0/A4O\":[\"チャンネルオーナー\"],\"V4qgxE\":[\"作成日時(以前、分前)\"],\"V8yTm6\":[\"検索をクリア\"],\"VJMMyz\":[\"ObsidianIRC — IRCを未来へ\"],\"VJScHU\":[\"理由\"],\"VLsmVV\":[\"通知をミュート\"],\"VbyRUy\":[\"コメント\"],\"Vmx0mQ\":[\"設定者:\"],\"VqnIZz\":[\"プライバシーポリシーとデータ取り扱い方針を見る\"],\"VrMygG\":[\"最小文字数は \",[\"0\"],\" 文字です\"],\"VrnTui\":[\"プロフィールに表示される代名詞\"],\"W8E3qn\":[\"認証済みアカウント\"],\"WAakm9\":[\"チャンネルを削除\"],\"WFxTHC\":[\"BANマスクを追加(例:nick!*@*、*!*@host.com)\"],\"WN1g9F\":[\"サーバーホストは必須です\"],\"WRYdXW\":[\"音声の再生位置\"],\"WUOH5B\":[\"ユーザーを無視\"],\"WWEXnZ\":[[\"0\",\"plural\",{\"other\":[\"さらに \",[\"1\"],\" 件表示\"]}]],\"WYxRzo\":[\"招待リンクを作成・管理する\"],\"Wd38W1\":[\"チャンネルを空欄にすると、汎用のネットワーク招待になります。説明はあなた専用の記録で、このリストで自分にのみ表示されます。\"],\"Weq9zb\":[\"一般\"],\"Wfj7Sk\":[\"通知音をミュートまたはミュート解除する\"],\"Wm7gbG\":[\"GitHub:\"],\"WyeHWY\":[\"*spam*\"],\"WzMCru\":[\"ユーザープロフィール\"],\"X6S3lt\":[\"設定、チャンネル、サーバーを検索...\"],\"XEHan5\":[\"このまま続行\"],\"XI1+wb\":[\"無効な形式\"],\"XIXeuC\":[\"@\",[\"0\"],\" へメッセージ\"],\"XMS+k4\":[\"プライベートメッセージを開始\"],\"XWgxXq\":[\"アルバム\"],\"Xd7+IT\":[\"プライベートチャットのピン留めを解除\"],\"Xm/s+u\":[\"表示\"],\"Xp2n93\":[\"サーバーの信頼済みファイルホストからのメディアを表示します。外部サービスへのリクエストは発生しません。\"],\"XvjC4F\":[\"保存中...\"],\"Y/qryO\":[\"検索に一致するユーザーが見つかりません\"],\"YAqRpI\":[[\"account\"],\" のアカウント登録に成功しました: \",[\"message\"]],\"YEfzvP\":[\"トピック保護 (+t)\"],\"YQOn6a\":[\"メンバーリストを折りたたむ\"],\"YRCoE9\":[\"チャンネルオペレーター\"],\"YURQaF\":[\"プロフィールを表示\"],\"YdBSvr\":[\"メディア表示と外部コンテンツを制御する\"],\"Yj6U3V\":[\"中央サーバーなし:\"],\"YjvpGx\":[\"代名詞\"],\"YqH4l4\":[\"キーなし\"],\"YyUPpV\":[\"アカウント:\"],\"ZJSWfw\":[\"サーバーから切断したときに表示されるメッセージ\"],\"ZR1dJ4\":[\"招待\"],\"ZdWg0V\":[\"ブラウザで開く\"],\"ZhRBbl\":[\"メッセージを検索…\"],\"Zmcu3y\":[\"詳細フィルター\"],\"a2/8e5\":[\"トピック設定日時(以降、分前)\"],\"aHKcKc\":[\"前のページ\"],\"aJTbXX\":[\"Operパスワード\"],\"aQryQv\":[\"パターンはすでに存在します\"],\"aW9pLN\":[\"チャンネルに参加できる最大ユーザー数。制限なしの場合は空欄にしてください。\"],\"ah4fmZ\":[\"YouTube、Vimeo、SoundCloudなどの既知のサービスのプレビューも表示します。\"],\"aifXak\":[\"このチャンネルにはメディアがありません\"],\"ap2zBz\":[\"緩め\"],\"az8lvo\":[\"オフ\"],\"azXSNo\":[\"メンバーリストを展開\"],\"azdliB\":[\"アカウントにログイン\"],\"b26wlF\":[\"彼女/彼女の\"],\"bD/+Ei\":[\"厳格\"],\"bQ6BJn\":[\"詳細なフラッド保護ルールを設定します。各ルールは、監視するアクティビティの種類と、しきい値を超えた場合に実行するアクションを指定します。\"],\"beV7+y\":[\"ユーザーは \",[\"channelName\"],\" への参加招待を受け取ります。\"],\"bk84cH\":[\"離席メッセージ\"],\"bkHdLj\":[\"IRCサーバーを追加\"],\"bmQLn5\":[\"ルールを追加\"],\"bwRvnp\":[\"アクション\"],\"c8+EVZ\":[\"認証済みアカウント\"],\"cGYUlD\":[\"メディアプレビューは読み込まれません。\"],\"cLF98o\":[\"コメントを表示 (\",[\"commentCount\"],\")\"],\"cLKIDO\":[\"利用可能なユーザーがいません\"],\"cSgpoS\":[\"プライベートチャットをピン留め\"],\"cde3ce\":[\"<0>\",[\"0\"],\"</0> へメッセージ\"],\"chQsxg\":[\"フォーマット済み出力をコピー\"],\"cl/A5J\":[[\"__DEFAULT_IRC_SERVER_NAME__\"],\" へようこそ!\"],\"cnGeoo\":[\"削除\"],\"coPLXT\":[\"IRC通信はサーバーに保存されません\"],\"crYH/6\":[\"SoundCloudプレーヤー\"],\"d3sis4\":[\"サーバーを追加\"],\"d9aN5k\":[[\"username\"],\" をチャンネルから削除する\"],\"dEgA5A\":[\"キャンセル\"],\"dGi1We\":[\"このプライベートメッセージの会話のピン留めを解除する\"],\"dJVuyC\":[[\"channelName\"],\" を退出しました (\",[\"reason\"],\")\"],\"dMtLDE\":[\"宛て\"],\"dXqxlh\":[\"<0>⚠️ セキュリティリスク!</0> この接続は傍受や中間者攻撃に対して脆弱な可能性があります。\"],\"da9Q/R\":[\"チャンネルモードを変更しました\"],\"dhJN3N\":[\"コメントを表示\"],\"dj2xTE\":[\"通知を閉じる\"],\"dpCzmC\":[\"フラッド保護設定\"],\"e9dQpT\":[\"このリンクを新しいタブで開きますか?\"],\"ePK91l\":[\"編集\"],\"eYBDuB\":[\"画像をアップロードするか、動的サイズ変換のための \",[\"size\"],\" 置換を含むURLを入力してください\"],\"edBbee\":[[\"username\"],\" をホストマスクでBANする(同じIP/ホストからの再参加を防止)\"],\"ekfzWq\":[\"ユーザー設定\"],\"elPDWs\":[\"IRCクライアントの使い心地をカスタマイズする\"],\"eu2osY\":[\"<0>💡 推奨事項:</0> このサーバーを信頼し、リスクを理解している場合のみ続行してください。この接続で機密情報やパスワードを共有しないようにしてください。\"],\"euEhbr\":[\"クリックして \",[\"channel\"],\" に参加\"],\"ez3vLd\":[\"複数行入力を有効にする\"],\"f0J5Ki\":[\"サーバー間通信に暗号化されていない接続が使用される可能性があります\"],\"f9BHJk\":[\"ユーザーに警告\"],\"fDOLLd\":[\"チャンネルが見つかりません。\"],\"ffzDkB\":[\"匿名分析:\"],\"fq1GF9\":[\"ユーザーがサーバーから切断したときに表示\"],\"gEF57C\":[\"このサーバーは1種類の接続タイプのみサポートしています\"],\"gJuLUI\":[\"無視リスト\"],\"gNzMrk\":[\"現在のアバター\"],\"gjPWyO\":[\"ニックネームを入力...\"],\"gz6UQ3\":[\"最大化\"],\"h6razj\":[\"チャンネル名マスクを除外\"],\"hG6jnw\":[\"トピックが設定されていません\"],\"hG89Ed\":[\"画像\"],\"hYgDIe\":[\"作成\"],\"hZ6znB\":[\"ポート\"],\"ha+Bz5\":[\"例:100:1440\"],\"he3ygx\":[\"コピー\"],\"hehnjM\":[\"数量\"],\"hzdLuQ\":[\"Voice以上の権限を持つユーザーのみ発言できます\"],\"i0qMbr\":[\"ホーム\"],\"iDNBZe\":[\"通知\"],\"iH8pgl\":[\"戻る\"],\"iL9SZg\":[\"ユーザーをBAN(ニックネーム)\"],\"iNt+3c\":[\"画像に戻る\"],\"iQvi+a\":[\"このサーバーのリンクセキュリティが低い場合に警告しない\"],\"iSLIjg\":[\"接続\"],\"iWXkHH\":[\"Halfop\"],\"iZeTtp\":[\"サーバーホスト\"],\"idD8Ev\":[\"保存済み\"],\"iivqkW\":[\"サインイン日時\"],\"ij+Elv\":[\"画像プレビュー\"],\"ilIWp7\":[\"通知を切り替え\"],\"iuaqvB\":[\"ワイルドカードには * を使用してください。例:baduser!*@*、*!*@spammer.com、troll*!*@*\"],\"ixkTse\":[\"ボット\"],\"j2DGR0\":[\"ホストマスクでBAN\"],\"jA4uoI\":[\"トピック:\"],\"jLXxGK\":[\"https://example.com\"],\"jPSk57\":[\"理由(任意)\"],\"jUV7CU\":[\"アバターをアップロード\"],\"jW5Uwh\":[\"外部メディアの読み込み範囲を制御します。オフ/安全/信頼できるソース/すべてのコンテンツ。\"],\"jXzms5\":[\"添付オプション\"],\"jZlrte\":[\"カラー\"],\"jfC/xh\":[\"連絡先\"],\"jywMpv\":[\"#new-channel-name\"],\"k112DD\":[\"古いメッセージを読み込む\"],\"k3ID0F\":[\"メンバーをフィルター…\"],\"k65gsE\":[\"詳細を見る\"],\"k7Zgob\":[\"接続をキャンセル\"],\"kAVx5h\":[\"招待が見つかりません\"],\"kCLEPU\":[\"接続先\"],\"kF5LKb\":[\"無視パターン:\"],\"kGeOx/\":[[\"0\"],\" に参加\"],\"kITKr8\":[\"チャンネルモードを読み込み中...\"],\"kPpPsw\":[\"あなたはIRC Operatorです\"],\"kWJmRL\":[\"あなた\"],\"kfcRb0\":[\"アバター\"],\"kjMqSj\":[\"JSONをコピー\"],\"krViRy\":[\"JSONとしてコピーするにはクリック\"],\"ks71ra\":[\"例外\"],\"kw4lRv\":[\"チャンネルハーフオペレーター\"],\"kxgIRq\":[\"チャンネルを選択または追加して始めましょう。\"],\"ky6dWe\":[\"アバタープレビュー\"],\"l+GxCv\":[\"チャンネルを読み込み中...\"],\"l+IUVW\":[[\"account\"],\" のアカウント確認に成功しました: \",[\"message\"]],\"l/siQz\":[[\"reconnectCount\",\"plural\",{\"other\":[[\"reconnectCount\"],\"回 再接続した\"]}]],\"l1l8sj\":[[\"0\"],\"日前\"],\"l5NhnV\":[\"#チャンネル(任意)\"],\"l5jmzx\":[[\"0\"],\" と \",[\"1\"],\" が入力中...\"],\"lCF0wC\":[\"更新\"],\"lHy8N5\":[\"さらにチャンネルを読み込み中...\"],\"lasgrr\":[\"使用済み\"],\"lbpf14\":[[\"value\"],\"に参加\"],\"lfFsZ4\":[\"チャンネル\"],\"lkNdiH\":[\"アカウント名\"],\"ln500L\":[\"ObsidianIRC\"],\"lnCMdg\":[\"画像をアップロード\"],\"loQxaJ\":[\"戻りました\"],\"lvfaxv\":[\"ホーム\"],\"m16xKo\":[\"追加\"],\"m8flAk\":[\"プレビュー(未アップロード)\"],\"mEPxTp\":[\"<0>⚠️ 注意!</0> 信頼できる送信元のリンクのみ開いてください。悪意のあるリンクはセキュリティやプライバシーを侵害する恐れがあります。\"],\"mHGdhG\":[\"サーバー情報\"],\"mHS8lb\":[\"#\",[\"0\"],\" へメッセージ\"],\"mMYBD9\":[\"広め — より広範な保護範囲\"],\"mTGsPd\":[\"チャンネルトピック\"],\"mU8j6O\":[\"外部メッセージ禁止 (+n)\"],\"mZp8FL\":[\"自動的に1行入力に切り替え\"],\"mdQu8G\":[\"YourNickname\"],\"miSSBQ\":[\"コメント (\",[\"commentCount\"],\")\"],\"mvyLSy\":[\"ユーザーは認証済みです\"],\"mwtcGl\":[\"コメントを閉じる\"],\"mzI/c+\":[\"ダウンロード\"],\"n3fGRk\":[[\"0\"],\" が設定\"],\"nE9jsU\":[\"緩め — 控えめな保護\"],\"nNflMD\":[\"チャンネルを退出\"],\"nPXkBi\":[\"WHOISデータを読み込み中...\"],\"nQnxxF\":[\"#\",[\"0\"],\" へメッセージ(Shift+Enterで改行)\"],\"nWMRxa\":[\"ピン留めを解除\"],\"nkC032\":[\"フラッドプロファイルなし\"],\"o69z4d\":[[\"username\"],\" に警告メッセージを送る\"],\"o9ylQi\":[\"GIFを検索して始めましょう\"],\"oFGkER\":[\"サーバー通知\"],\"oOi11l\":[\"最下部にスクロール\"],\"oPYIL5\":[\"ネットワーク\"],\"oQEzQR\":[\"新しいDM\"],\"oXOSPE\":[\"オンライン\"],\"oal760\":[\"サーバーリンクへの中間者攻撃が可能な状態です\"],\"oeqmmJ\":[\"信頼できるソース\"],\"optX0N\":[[\"0\"],\"時間前\"],\"ovBPCi\":[\"デフォルト\"],\"p0Z69r\":[\"パターンを空にすることはできません\"],\"p1KgtK\":[\"音声の読み込みに失敗しました\"],\"p59pEv\":[\"詳細情報\"],\"p7sRI6\":[\"入力中であることを他のユーザーに知らせる\"],\"pBm1od\":[\"シークレットチャンネル\"],\"pNmiXx\":[\"すべてのサーバーで使用するデフォルトのニックネーム\"],\"pUUo9G\":[\"ホスト名:\"],\"pVGPmz\":[\"アカウントパスワード\"],\"peNE68\":[\"永続的\"],\"plhHQt\":[\"データなし\"],\"pm6+q5\":[\"セキュリティ警告\"],\"pn5qSs\":[\"追加情報\"],\"q0cR4S\":[\"は現在 **\",[\"newNick\"],\"** として知られています\"],\"qFcunY\":[\"LIST または NAMES コマンドにチャンネルが表示されません\"],\"qLpTm/\":[\"リアクション \",[\"emoji\"],\" を削除\"],\"qVkGWK\":[\"ピン留め\"],\"qY8wNa\":[\"ホームページ\"],\"qb0xJ7\":[\"ワイルドカードを使用してください:* は任意の文字列、? は任意の1文字に一致します。例:nick!*@*、*!*@host.com、*!*user@*\"],\"qhzpRq\":[\"チャンネルキー (+k)\"],\"qtoOYG\":[\"制限なし\"],\"r1W2AS\":[\"ファイルホスト画像\"],\"rIPR2O\":[\"トピック設定日時(以前、分前)\"],\"rMMSYo\":[\"最大文字数は \",[\"0\"],\" 文字です\"],\"rWtzQe\":[\"ネットワークが分割され、再接続されました。✅\"],\"rYG2u6\":[\"しばらくお待ちください...\"],\"rdUucN\":[\"プレビュー\"],\"rjGI/Q\":[\"プライバシー\"],\"rk8iDX\":[\"GIFを読み込み中...\"],\"rn6SBY\":[\"ミュート解除\"],\"s/UKqq\":[\"チャンネルからキックされました\"],\"s8cATI\":[[\"channelName\"],\" に参加しました\"],\"sCO9ue\":[\"<0>\",[\"serverName\"],\"</0> への接続には次のセキュリティ上の懸念事項があります:\"],\"sGH11W\":[\"サーバー\"],\"sHI1H+\":[\"は現在 **\",[\"newNick\"],\"** として知られています\"],\"sJyV04\":[[\"inviter\"],\" があなたを \",[\"channel\"],\" に招待しました\"],\"sby+1/\":[\"クリックしてコピー\"],\"sfN25C\":[\"本名またはフルネーム\"],\"sliuzR\":[\"リンクを開く\"],\"sqrO9R\":[\"カスタムメンション\"],\"sr6RdJ\":[\"Shift+Enterで複数行入力\"],\"swrCpB\":[[\"user\"],\" がチャンネルを \",[\"oldName\"],\" から \",[\"newName\"],\" に変更しました\",[\"0\"]],\"sxkWRg\":[\"詳細設定\"],\"t/YqKh\":[\"削除\"],\"t47eHD\":[\"このサーバーでの固有識別子\"],\"tAkAh0\":[\"動的サイズ変換のための \",[\"size\"],\" 置換を含むURL。例:https://example.com/avatar/\",[\"size\"],\"/channel.jpg\"],\"tXLJS3\":[\"チャンネルリストのサイドバーを表示/非表示にする\"],\"tfDRzk\":[\"保存\"],\"tiBsJk\":[[\"channelName\"],\" を退出しました\"],\"tt4/UD\":[\"退出しました (\",[\"reason\"],\")\"],\"u0TcnO\":[\"ニックネーム {nick} は既に使用中です。{newNick} で再試行します\"],\"u0a8B4\":[\"管理者アクセスのためにIRC Operatorとして認証する\"],\"u0rWFU\":[\"作成日時(以降、分前)\"],\"u72w3t\":[\"無視するユーザーとパターン\"],\"u7jc2L\":[\"退出しました\"],\"uAQUqI\":[\"ステータス\"],\"uB85T3\":[\"保存失敗:\",[\"msg\"]],\"uV3DOL\":[\"G-Line\"],\"uW3lLI\":[\"IRCサーバー:\"],\"ukyW4o\":[\"あなたの招待リンク\"],\"usSSr/\":[\"ズームレベル\"],\"v7uvcf\":[\"ソフトウェア:\"],\"vE8kb+\":[\"Shift+Enterで改行(Enterで送信)\"],\"vERlcd\":[\"プロフィール\"],\"vK0RL8\":[\"トピックなし\"],\"vSJd18\":[\"動画\"],\"vXIe7J\":[\"言語\"],\"vaHYxN\":[\"本名\"],\"vhjbKr\":[\"離席中\"],\"w4NYox\":[[\"title\"],\" クライアント\"],\"w8xQRx\":[\"無効な値\"],\"wFjjxZ\":[[\"username\"],\" によって \",[\"channelName\"],\" からキックされました (\",[\"reason\"],\")\"],\"wGjaGl\":[\"BAN例外が見つかりません\"],\"wPrGnM\":[\"チャンネル管理者\"],\"wRkP2d\":[\"GIF\"],\"wbm86v\":[\"ユーザーがチャンネルに参加・退出したときに表示する\"],\"whqZ9r\":[\"ハイライトする追加の単語またはフレーズ\"],\"wm7RV4\":[\"通知音\"],\"wz/Yoq\":[\"サーバー間で中継される際にメッセージが傍受される可能性があります\"],\"x3+y8b\":[\"このリンクから登録した人数\"],\"xCJdfg\":[\"クリア\"],\"xOTzt5\":[\"たった今\"],\"xUHRTR\":[\"接続時に自動的にOperatorとして認証する\"],\"xWHwwQ\":[\"BAN一覧\"],\"xYilR2\":[\"メディア\"],\"xbi8D6\":[\"このサーバーは招待リンクに対応していません(<0>obby.world/invitation</0>ケイパビリティが告知されていません)。通常通りチャットは利用できます。このパネルはobbyircdベースのネットワーク用です。\"],\"xceQrO\":[\"安全なWebSocketのみサポートされています\"],\"xdtXa+\":[\"チャンネル名\"],\"xfXC7q\":[\"テキストチャンネル\"],\"xlCYOE\":[\"メッセージを取得中...\"],\"xlhswE\":[\"最小値は \",[\"0\"],\" です\"],\"xq97Ci\":[\"単語またはフレーズを追加...\"],\"xuRqRq\":[\"クライアント制限 (+l)\"],\"xwF+7J\":[[\"0\"],\" が入力中...\"],\"y1eoq1\":[\"リンクをコピー\"],\"yNeucF\":[\"このサーバーは拡張プロフィールメタデータ(IRCv3 METADATA拡張)をサポートしていません。アバター、表示名、ステータスなどの追加フィールドは利用できません。\"],\"yPlrca\":[\"チャンネルアバター\"],\"yQE2r9\":[\"読み込み中\"],\"ySU+JY\":[\"your@email.com\"],\"yTX1Rt\":[\"Operユーザー名\"],\"yYOzWD\":[\"ログ\"],\"yfx9Re\":[\"IRC Operatorパスワード\"],\"ygCKqB\":[\"停止\"],\"ymDxJx\":[\"IRC Operatorユーザー名\"],\"yrpRsQ\":[\"名前順で並び替え\"],\"yz7wBu\":[\"閉じる\"],\"zJw+jA\":[\"モードを設定: \",[\"0\"]],\"zbymaY\":[[\"0\"],\"分前\"],\"zebeLu\":[\"Operユーザー名を入力\"],\"zpr0Bw\":[\"GZ-Line\"]}"); No newline at end of file
/*eslint-disable*/export const messages=JSON.parse("{\"+5VMrz\":[\"無効なパターン形式です。nick!user@host 形式を使用してください(ワイルドカード * 使用可)\"],\"+6NQQA\":[\"一般サポートチャンネル\"],\"+6NyRG\":[\"クライアント\"],\"+K0AvT\":[\"切断\"],\"+cyFdH\":[\"離席時に表示するデフォルトメッセージ\"],\"+mVPqU\":[\"メッセージ内のMarkdown書式を表示する\"],\"+vqCJH\":[\"認証用のアカウントユーザー名\"],\"+yPBXI\":[\"ファイルを選択\"],\"+zy2Nq\":[\"種類\"],\"/09cao\":[\"リンクセキュリティが低い(レベル \",[\"securityLevel\"],\")\"],\"/3BQ4J\":[\"チャンネル外のユーザーはメッセージを送信できません\"],\"/4C8U0\":[\"すべてコピー\"],\"/6BzZF\":[\"メンバーリストを切り替え\"],\"/AkXyp\":[\"確認しますか?\"],\"/TNOPk\":[\"ユーザーは離席中です\"],\"/XQgft\":[\"探す\"],\"/cF7Rs\":[\"音量\"],\"/dqduX\":[\"次のページ\"],\"/fc3q4\":[\"すべてのコンテンツ\"],\"/kISDh\":[\"通知音を有効にする\"],\"/n04sB\":[\"Kill\"],\"/rTz0M\":[\"音声\"],\"/rfkZe\":[\"メンションやメッセージに対してサウンドを再生する\"],\"0/0ZGA\":[\"チャンネル名マスク\"],\"0D6j7U\":[\"カスタムルールについて詳しく →\"],\"0XsHcR\":[\"ユーザーをキック\"],\"0ZpE//\":[\"ユーザー数順で並び替え\"],\"0bEPwz\":[\"離席中に設定\"],\"0dGkPt\":[\"チャンネルリストを展開\"],\"0gS7M5\":[\"表示名\"],\"0kS+M8\":[\"サンプルNET\"],\"0rgoY7\":[\"自分で選んだサーバーにのみ接続します\"],\"0wdd7X\":[\"参加\"],\"0wkVYx\":[\"プライベートメッセージ\"],\"111uHX\":[\"リンクプレビュー\"],\"196EG4\":[\"プライベートチャットを削除\"],\"1DSr1i\":[\"アカウントを登録する\"],\"1O/24y\":[\"チャンネルリストを切り替え\"],\"1TNIig\":[\"開く\"],\"1VPJJ2\":[\"外部リンクの警告\"],\"1ZC/dv\":[\"未読のメンションやメッセージはありません\"],\"1pO1zi\":[\"サーバー名は必須です\"],\"1uwfzQ\":[\"チャンネルトピックを表示\"],\"268g7c\":[\"表示名を入力\"],\"2CEOW6\":[\"soju バウンサー経由でバインドされたネットワーク\"],\"2F9+AZ\":[\"まã\xA0生のIRCトラフィックを取得していません。接続するかメッセージを送信してくã\xA0さい。\"],\"2FOFq1\":[\"ネットワーク上のサーバーオペレーターがメッセージを閲覧できる可能性があります\"],\"2FYpfJ\":[\"詳細\"],\"2HF1Y2\":[[\"inviter\"],\" が \",[\"target\"],\" を \",[\"channel\"],\" に招待しました\"],\"2I70QL\":[\"ユーザープロフィール情報を表示する\"],\"2QYdmE\":[\"ユーザー:\"],\"2QpEjG\":[\"退出しました\"],\"2YE223\":[\"#\",[\"0\"],\" へメッセージ(Enterで改行、Shift+Enterで送信)\"],\"2bimFY\":[\"サーバーパスワードを使用する\"],\"2iTmdZ\":[\"ローカルストレージ:\"],\"2odkwe\":[\"厳格 — より積極的な保護\"],\"2uDhbA\":[\"招待するユーザー名を入力\"],\"2ygf/L\":[\"← 戻る\"],\"2zEgxj\":[\"GIFを検索...\"],\"3RdPhl\":[\"チャンネル名を変更\"],\"3THokf\":[\"Voiceユーザー\"],\"3TSz9S\":[\"最小化\"],\"3jBDvM\":[\"チャンネル表示名\"],\"3ryuFU\":[\"アプリ改善のための任意のクラッシュレポート\"],\"3uBF/8\":[\"ビューアを閉じる\"],\"3uwW8F\":[\"https://example.com/avatar/\",[\"size\"],\"/channel.jpg\"],\"3xf8Kz\":[\"アカウント名を入力...\"],\"4/Rr0R\":[\"現在のチャンネルにユーザーを招待する\"],\"4EZrJN\":[\"ルール\"],\"4JJtW9\":[\"#overflow\"],\"4NqeT4\":[\"フラッドプロファイル (+F)\"],\"4RZQRK\":[\"今何してるの?\"],\"4hfTrB\":[\"ニックネーム\"],\"4n99LO\":[\"すでに \",[\"0\"],\" にいます\"],\"4t6vMV\":[\"短いメッセージの場合は自動的に1行入力に切り替える\"],\"4vsHmf\":[\"時間(分)\"],\"4x/Axu\":[\"バウンサーにはまだネットワークがありません。最初のネットワークを追加して始めましょう。\"],\"5+INAX\":[\"自分へのメンションを含むメッセージをハイライトする\"],\"5R5Pv/\":[\"Oper名\"],\"678PKt\":[\"ネットワーク名\"],\"6Aih4U\":[\"オフライン\"],\"6CO3WE\":[\"チャンネルに参加するために必要なパスワード。キーを削除するには空欄にしてください。\"],\"6HhMs3\":[\"退出メッセージ\"],\"6V3Ea3\":[\"コピーしました\"],\"6lGV3K\":[\"折りたたむ\"],\"6yFOEi\":[\"oper パスワードを入力...\"],\"7+IHTZ\":[\"ファイルが選択されていません\"],\"73fnil\":[\"TLS\"],\"73hrRi\":[\"nick!user@host(例:spam*!*@*、*!*@badhost.com)\"],\"7QkKyN\":[\"プライベートメッセージを送信\"],\"7U1W7c\":[\"とても緩め\"],\"7Y1YQj\":[\"本名:\"],\"7YHArF\":[\"— ビューアで開く\"],\"7fjnVl\":[\"ユーザーを検索...\"],\"7jL88x\":[\"このメッセージを削除しますか?この操作は元に戻せません。\"],\"7nGhhM\":[\"今どんな気分ですか?\"],\"7sEpu1\":[\"メンバー — \",[\"0\"]],\"7sNhEz\":[\"ユーザー名\"],\"8H0Q+x\":[\"プロファイルについて詳しく →\"],\"8Phu0A\":[\"ユーザーがニックネームを変更したときに表示する\"],\"8XTG9e\":[\"Operパスワードを入力\"],\"8XsV2J\":[\"再送信\"],\"8ZsakT\":[\"パスワード\"],\"8kR84m\":[\"外部リンクを開こうとしています:\"],\"8lCgih\":[\"ルールを削除\"],\"8o3dPc\":[\"ファイルをドロップしてアップロード\"],\"8p/xVT\":[[\"0\",\"plural\",{\"one\":[[\"1\"]],\"other\":[[\"2\"]]}]],\"8wRzac\":[[\"joinCount\",\"plural\",{\"other\":[[\"joinCount\"],\"回 参加した\"]}]],\"9BMLnJ\":[\"サーバーに再接続\"],\"9OEgyT\":[\"リアクションを追加\"],\"9PQ8m2\":[\"G-Line(グローバルBAN)\"],\"9Qs99X\":[\"メール:\"],\"9QupBP\":[\"パターンを削除\"],\"9W7tl5\":[\"(変更なし)\"],\"9bG48P\":[\"送信中\"],\"9f5f0u\":[\"プライバシーに関するご質問はこちら:\"],\"9iweoP\":[[\"0\"],\" のネットワーク\"],\"9unqs3\":[\"退席中:\"],\"9v3hwv\":[\"サーバーが見つかりません。\"],\"9zb2WA\":[\"接続中\"],\"A1taO8\":[\"検索\"],\"A2adVi\":[\"入力中通知を送信する\"],\"A9Rhec\":[\"チャンネル名\"],\"AWOSPo\":[\"ズームイン\"],\"AXSpEQ\":[\"接続時にOperになる\"],\"AeXO77\":[\"アカウント\"],\"AhNP40\":[\"シーク\"],\"Ai2U7L\":[\"ホスト\"],\"AjBQnf\":[\"ニックネームを変更しました\"],\"AmXVh6\":[\"https://example.com/avatar.png\"],\"AnRu/j\":[\"返信をキャンセル\"],\"ApSx0O\":[\"\\\"\",[\"searchQuery\"],\"\\\"に一致するメッセージが\",[\"0\"],\"件見つかりました\"],\"AxPAXW\":[\"結果が見つかりません\"],\"AyNqAB\":[\"すべてのサーバーイベントをチャットに表示する\"],\"B/QqGw\":[\"席を外しています\"],\"B0sB2k\":[\"平文\"],\"B8AaMI\":[\"この項目は必須です\"],\"BA2c49\":[\"このサーバーは高度なLISTフィルタリングをサポートしていません\"],\"BDKt3I\":[[\"0\"],\"、\",[\"1\"],\"、\",[\"2\"],\" と他 \",[\"3\"],\" 人が入力中...\"],\"BGul2A\":[\"保存されていない変更があります。保存せずに閉じてもよいですか?\"],\"BIf9fi\":[\"ステータスメッセージ\"],\"BPm98R\":[\"サーバーが選択されていません。まずサイドバーからサーバーを選択してくã\xA0さい。招待リンクはサーバーごとに管理されます。\"],\"BZz3md\":[\"個人ウェブサイト\"],\"Bgm/H7\":[\"複数行のテキスト入力を許可する\"],\"BiQIl1\":[\"このプライベートメッセージの会話をピン留めする\"],\"BlNZZ2\":[\"クリックしてメッセージに移動\"],\"Bowq3c\":[\"オペレーターのみがチャンネルトピックを変更できます\"],\"Btozzp\":[\"この画像の有効期限が切れています\"],\"Bycfjm\":[\"合計:\",[\"0\"]],\"C6IBQc\":[\"JSON全体をコピー\"],\"C9L9wL\":[\"データ収集\"],\"CDq4wC\":[\"ユーザーをモデレート\"],\"CHVRxG\":[\"@\",[\"0\"],\" へメッセージ(Shift+Enterで改行)\"],\"CN9zdR\":[\"Oper名とパスワードは必須です\"],\"CW3sYa\":[\"リアクション \",[\"emoji\"],\" を追加\"],\"CaAkqd\":[\"退出を表示\"],\"CbvaYj\":[\"ニックネームでBAN\"],\"CcK+Ft\":[\"PDF\"],\"Ce8q3L\":[\"チャンネルを選択\"],\"CsekCi\":[\"通常\"],\"D+NlUC\":[\"システム\"],\"D28t6+\":[\"参加して退出しました\"],\"DB8zMK\":[\"適用\"],\"DBcWHr\":[\"カスタム通知音ファイル\"],\"DTy9Xw\":[\"メディアプレビュー\"],\"Dj4pSr\":[\"安全なパスワードを選択してください\"],\"Du+zn+\":[\"検索中...\"],\"Du2T2f\":[\"設定が見つかりません\"],\"DwsSVQ\":[\"フィルターを適用して更新\"],\"E3W/zd\":[\"デフォルトニックネーム\"],\"E6nRW7\":[\"URLをコピー\"],\"E703RG\":[\"モード:\"],\"EAeu1Z\":[\"招待を送信\"],\"EFKJQT\":[\"設定\"],\"EGPQBv\":[\"カスタムフラッドルール (+f)\"],\"ELik0r\":[\"プライバシーポリシー全文を見る\"],\"EPbeC2\":[\"チャンネルトピックを表示または編集する\"],\"EQCDNT\":[\"oper ユーザー名を入力...\"],\"EUvulZ\":[\"\\\"\",[\"searchQuery\"],\"\\\"に一致するメッセージが1件見つかりました\"],\"EatZYJ\":[\"次の画像\"],\"EdQY6l\":[\"なし\"],\"EnqLYU\":[\"サーバーを検索...\"],\"F0OKMc\":[\"サーバーを編集\"],\"F6Int2\":[\"ハイライトを有効にする\"],\"FDoLyE\":[\"最大ユーザー数\"],\"FUU/hZ\":[\"チャットで読み込む外部メディアの量を制御します。\"],\"Fdp03t\":[\"オン\"],\"FfPWR0\":[\"モーダル\"],\"FjkaiT\":[\"ズームアウト\"],\"FlqOE9\":[\"これが意味すること:\"],\"FolHNl\":[\"アカウントと認証を管理する\"],\"Fp2Dif\":[\"サーバーを退出しました\"],\"G5KmCc\":[\"GZ-Line(グローバルZ-Line)\"],\"GDs0lz\":[\"<0>リスク:</0> 機密情報(メッセージ、プライベート会話、認証情報)が、IRCサーバー間に位置するネットワーク管理者や攻撃者に露出する可能性があります。\"],\"GR+2I3\":[\"招待マスクを追加(例:nick!*@*、*!*@host.com)\"],\"GRLyMU\":[\"ポップアウトしたサーバー通知を閉じる\"],\"GdhD7H\":[\"もう一度クリックして確認\"],\"GlHnXw\":[\"ニックネームの変更に失敗しました: \",[\"error\"],\" \",[\"0\"]],\"GswZF3\":[\"プレビュー:\"],\"GtmO8/\":[\"から\"],\"GtuHUQ\":[\"サーバー上でこのチャンネルの名前を変更します。すべてのユーザーに新しい名前が表示されます。\"],\"GuGfFX\":[\"検索を切り替え\"],\"GxkJXS\":[\"アップロード中...\"],\"GzbwnK\":[\"チャンネルに参加しました\"],\"GzsUDB\":[\"拡張プロフィール\"],\"H/PnT8\":[\"絵文字を挿入\"],\"H6Izzl\":[\"お好みのカラーコード\"],\"H9jIv+\":[\"参加/退出を表示\"],\"HAKBY9\":[\"ファイルをアップロード\"],\"HdE1If\":[\"チャンネル\"],\"Hk4AW9\":[\"お好みの表示名\"],\"HmHDk7\":[\"メンバーを選択\"],\"HrQzPU\":[[\"networkName\"],\" のチャンネル\"],\"I2tXQ5\":[\"@\",[\"0\"],\" へメッセージ(Enterで改行、Shift+Enterで送信)\"],\"I6bw/h\":[\"ユーザーをBAN\"],\"I92Z+b\":[\"通知を有効にする\"],\"I9D72S\":[\"このメッセージを削除してもよいですか?この操作は元に戻せません。\"],\"IA+1wo\":[\"ユーザーがチャンネルからキックされたときに表示する\"],\"IDwkJx\":[\"IRC Operator\"],\"ILlU+s\":[\"情報:\"],\"IUwGEM\":[\"変更を保存\"],\"IVeGK6\":[[\"0\"],\"、\",[\"1\"],\"、\",[\"2\"],\" が入力中...\"],\"IgrLD/\":[\"一時停止\"],\"Im6JED\":[\"WHISPER\"],\"ImOQa9\":[\"返信\"],\"IoHMnl\":[\"最大値は \",[\"0\"],\" です\"],\"IvMj+0\":[\"Op\"],\"J28zul\":[\"接続中...\"],\"J5T9NW\":[\"ユーザー情報\"],\"J8Y5+z\":[\"おっと!ネットワーク分割が発生しました!⚠️\"],\"JBHkBA\":[\"チャンネルを退出しました\"],\"JCwL0Q\":[\"理由を入力(任意)\"],\"JFciKP\":[\"切り替え\"],\"JXGkhG\":[\"チャンネル名を変更する(オペレーターのみ)\"],\"JcD7qf\":[\"その他のアクション\"],\"JdkA+c\":[\"シークレット (+s)\"],\"Jmu12l\":[\"サーバーチャンネル\"],\"JvQ++s\":[\"Markdownを有効にする\"],\"K2jwh/\":[\"WHOISデータがありません\"],\"KAXSwC\":[\"Voice\"],\"KDfTdX\":[\"メッセージを削除\"],\"KKBlUU\":[\"埋め込み\"],\"KM0pLb\":[\"チャンネルへようこそ!\"],\"KR6W2h\":[\"無視を解除\"],\"KV+Bi1\":[\"招待制 (+i)\"],\"KdCtwE\":[\"カウンターをリセットするまでフラッドアクティビティを監視する秒数\"],\"Kkezga\":[\"サーバーパスワード\"],\"KsiQ/8\":[\"ユーザーはチャンネルに参加するために招待が必要です\"],\"L+gB/D\":[\"チャンネル情報\"],\"LC1a7n\":[\"IRCサーバーは、サーバー間リンクのセキュリティレベルが低いと報告しています。これは、ネットワーク内のIRCサーバー間でメッセージが中継される際に、適切に暗号化されていないか、SSL/TLS証明書が正しく検証されない可能性があることを意味します。\"],\"LEwpeL\":[\"soju バウンサー(制御)\"],\"LNfLR5\":[\"キックを表示\"],\"LP+1Z7\":[\"ネットワークを追加\"],\"LQb0W/\":[\"すべてのイベントを表示\"],\"LU7/yA\":[\"UI上で表示するための別名です。スペース、絵文字、特殊文字を含めることができます。実際のチャンネル名(\",[\"channelName\"],\")は引き続きIRCコマンドで使用されます。\"],\"LUb9O7\":[\"有効なサーバーポートが必要です\"],\"LV4fT6\":[\"説明(任意、例:「Q3ベータテスター」)\"],\"Lb+BUl\":[\"https://example.com/avatar.jpg\"],\"LcET2C\":[\"プライバシーポリシー\"],\"LcuSDR\":[\"プロフィール情報とメタデータを管理する\"],\"LqLS9B\":[\"ニックネーム変更を表示\"],\"LsDQt2\":[\"チャンネル設定\"],\"LtI9AS\":[\"オーナー\"],\"LuNhhL\":[\"このメッセージにリアクションしました\"],\"M/AZNG\":[\"アバター画像のURL\"],\"M/WIer\":[\"メッセージを送信\"],\"M8er/5\":[\"名前:\"],\"MHk+7g\":[\"前の画像\"],\"MRorGe\":[\"DMを送る\"],\"MVbSGP\":[\"時間ウィンドウ(秒)\"],\"MkpcsT\":[\"メッセージと設定はデバイス上にローカルで保存されます\"],\"MzPdC2\":[\"サーバーパスワード (PASS)\"],\"N/hDSy\":[\"ボットとしてマーク — 通常は「on」または空欄\"],\"N6j2JH\":[[\"0\"],\" を編集\"],\"N7TQbE\":[[\"channelName\"],\" にユーザーを招待\"],\"NCca/o\":[\"デフォルトニックネームを入力...\"],\"Nqs6B9\":[\"すべての外部メディアを表示します。URLが不明なサーバーへのリクエストを引き起こす場合があります。\"],\"Nt+9O7\":[\"生のTCPの代わりにWebSocketを使用する\"],\"NxIHzc\":[\"ユーザーを切断\"],\"O+v/cL\":[\"サーバー上のすべてのチャンネルを一覧表示\"],\"OCGpR4\":[\"(継承)\"],\"ODwSCk\":[\"GIFを送信\"],\"OGQ5kK\":[\"通知音とハイライトを設定する\"],\"OIPt1Z\":[\"メンバーリストのサイドバーを表示/非表示にする\"],\"OKSNq/\":[\"とても厳格\"],\"ONWvwQ\":[\"アップロード\"],\"OVKoQO\":[\"認証用のアカウントパスワード\"],\"ObsidianIRC - Bringing IRC to the future\":[\"ObsidianIRC - IRCを未来へ\"],\"OhCpra\":[\"トピックを設定…\"],\"OkltoQ\":[[\"username\"],\" をニックネームでBANする(同じニックネームでの再参加を防止)\"],\"P+t/Te\":[\"追加データなし\"],\"P42Wcc\":[\"安全\"],\"PD38l0\":[\"チャンネルアバタープレビュー\"],\"PD9mEt\":[\"メッセージを入力...\"],\"PPqfdA\":[\"チャンネル設定を開く\"],\"PSCjfZ\":[\"このチャンネルに表示されるトピックです。すべてのユーザーがトピックを閲覧できます。\"],\"PZCecv\":[\"PDFプレビュー\"],\"PeLgsC\":[[\"c\",\"plural\",{\"other\":[[\"c\"],\"回\"]}]],\"PguS2C\":[\"例外マスクを追加(例:nick!*@*、*!*@host.com)\"],\"Pil5Ty\":[[\"0\"],\" 件中 \",[\"displayedChannelsCount\"],\" 件を表示中\"],\"PqhVlJ\":[\"ユーザーをBAN(ホストマスク)\"],\"Q+chwU\":[\"ユーザー名:\"],\"Q2QY4/\":[\"この招待を削除\"],\"Q3v9Wc\":[\"はい、削除します\"],\"Q6hhn8\":[\"設定\"],\"QF4a34\":[\"ユーザー名を入力してください\"],\"QGqSZ2\":[\"カラーと書式設定\"],\"QJQd1J\":[\"プロフィールを編集\"],\"QSzGDE\":[\"アイドル\"],\"QUlny5\":[[\"0\"],\" へようこそ!\"],\"Qoq+GP\":[\"もっと読む\"],\"QuSkCF\":[\"チャンネルをフィルター...\"],\"QwUrDZ\":[\"トピックを変更しました: \",[\"topic\"]],\"R0UH07\":[[\"1\"],\"枚中\",[\"0\"],\"枚目の画像\"],\"R7SsBE\":[\"ミュート\"],\"R8rf1X\":[\"クリックしてトピックを設定\"],\"RArB3D\":[[\"username\"],\" によって \",[\"channelName\"],\" からキックされました\"],\"RI3cWd\":[\"ObsidianIRCでIRCの世界を探索しよう\"],\"RIfHS5\":[\"新しい招待リンクを作成\"],\"RMMaN5\":[\"モデレート制 (+m)\"],\"RWw9Lg\":[\"モーダルを閉じる\"],\"RZ2BuZ\":[[\"account\"],\" のアカウント登録には確認が必要です: \",[\"message\"]],\"RySp6q\":[\"コメントを非表示\"],\"S5Togi\":[\"バウンサーからネットワークを読み込み中…\"],\"SPKQTd\":[\"ニックネームは必須です\"],\"SPVjfj\":[\"空欄の場合は「理由なし」がデフォルトになります\"],\"SQKPvQ\":[\"ユーザーを招待\"],\"STmlpb\":[\"ネットワーク一覧に戻る\"],\"SkZcl+\":[\"定義済みのフラッド保護プロファイルを選択してください。これらのプロファイルは、さまざまなユースケースに対してバランスの取れた保護設定を提供します。\"],\"Slr+3C\":[\"最小ユーザー数\"],\"Spnlre\":[[\"target\"],\" を \",[\"channel\"],\" に招待しました\"],\"T/ckN5\":[\"ビューアで開く\"],\"T91vKp\":[\"再生\"],\"TV2Wdu\":[\"データの取り扱いとプライバシー保護について詳しく見る。\"],\"TgFpwD\":[\"適用中...\"],\"TkzSFB\":[\"変更なし\"],\"TtserG\":[\"本名を入力\"],\"Ttz9J1\":[\"パスワードを入力...\"],\"Tz0i8g\":[\"設定\"],\"U3pytU\":[\"管理者\"],\"UDb2YD\":[\"リアクション\"],\"UE4KO5\":[\"*channel*\"],\"UETAwW\":[\"まã\xA0招待リンクを作成していません。上のフォーãƒ\xA0から最初の招待リンクを作成してくã\xA0さい。\"],\"UGT5vp\":[\"設定を保存\"],\"UV5hLB\":[\"BANが見つかりません\"],\"Uaj3Nd\":[\"ステータスメッセージ\"],\"Ue3uny\":[\"デフォルト(プロファイルなし)\"],\"UkARhe\":[\"通常 — 標準的な保護\"],\"Umn7Cj\":[\"まだコメントはありません。最初のコメントを投稿しましょう!\"],\"UtUIRh\":[[\"0\"],\" 件の古いメッセージ\"],\"UwzP+U\":[\"セキュア接続\"],\"V0/A4O\":[\"チャンネルオーナー\"],\"V4qgxE\":[\"作成日時(以前、分前)\"],\"V8yTm6\":[\"検索をクリア\"],\"VJMMyz\":[\"ObsidianIRC — IRCを未来へ\"],\"VJScHU\":[\"理由\"],\"VLsmVV\":[\"通知をミュート\"],\"VbyRUy\":[\"コメント\"],\"Vmx0mQ\":[\"設定者:\"],\"VqnIZz\":[\"プライバシーポリシーとデータ取り扱い方針を見る\"],\"VrMygG\":[\"最小文字数は \",[\"0\"],\" 文字です\"],\"VrnTui\":[\"プロフィールに表示される代名詞\"],\"W8E3qn\":[\"認証済みアカウント\"],\"WAakm9\":[\"チャンネルを削除\"],\"WFxTHC\":[\"BANマスクを追加(例:nick!*@*、*!*@host.com)\"],\"WN1g9F\":[\"サーバーホストは必須です\"],\"WRYdXW\":[\"音声の再生位置\"],\"WUOH5B\":[\"ユーザーを無視\"],\"WWEXnZ\":[[\"0\",\"plural\",{\"other\":[\"さらに \",[\"1\"],\" 件表示\"]}]],\"WYxRzo\":[\"招待リンクを作成・管理する\"],\"Wd38W1\":[\"チャンネルを空欄にすると、汎用のネットワーク招待になります。説明はあなた専用の記録で、このリストで自分にのみ表示されます。\"],\"Weq9zb\":[\"一般\"],\"Wfj7Sk\":[\"通知音をミュートまたはミュート解除する\"],\"Wm7gbG\":[\"GitHub:\"],\"WyeHWY\":[\"*spam*\"],\"WzMCru\":[\"ユーザープロフィール\"],\"X6S3lt\":[\"設定、チャンネル、サーバーを検索...\"],\"XEHan5\":[\"このまま続行\"],\"XI1+wb\":[\"無効な形式\"],\"XIXeuC\":[\"@\",[\"0\"],\" へメッセージ\"],\"XMS+k4\":[\"プライベートメッセージを開始\"],\"XWgxXq\":[\"アルバム\"],\"Xd7+IT\":[\"プライベートチャットのピン留めを解除\"],\"Xm/s+u\":[\"表示\"],\"Xp2n93\":[\"サーバーの信頼済みファイルホストからのメディアを表示します。外部サービスへのリクエストは発生しません。\"],\"XvjC4F\":[\"保存中...\"],\"Y/qryO\":[\"検索に一致するユーザーが見つかりません\"],\"YAqRpI\":[[\"account\"],\" のアカウント登録に成功しました: \",[\"message\"]],\"YEfzvP\":[\"トピック保護 (+t)\"],\"YQOn6a\":[\"メンバーリストを折りたたむ\"],\"YRCoE9\":[\"チャンネルオペレーター\"],\"YURQaF\":[\"プロフィールを表示\"],\"YdBSvr\":[\"メディア表示と外部コンテンツを制御する\"],\"Yj6U3V\":[\"中央サーバーなし:\"],\"YjvpGx\":[\"代名詞\"],\"YqH4l4\":[\"キーなし\"],\"YyUPpV\":[\"アカウント:\"],\"ZJSWfw\":[\"サーバーから切断したときに表示されるメッセージ\"],\"ZR1dJ4\":[\"招待\"],\"ZdWg0V\":[\"ブラウザで開く\"],\"ZhRBbl\":[\"メッセージを検索…\"],\"Zmcu3y\":[\"詳細フィルター\"],\"a2/8e5\":[\"トピック設定日時(以降、分前)\"],\"aHKcKc\":[\"前のページ\"],\"aJTbXX\":[\"Operパスワード\"],\"aQryQv\":[\"パターンはすでに存在します\"],\"aW9pLN\":[\"チャンネルに参加できる最大ユーザー数。制限なしの場合は空欄にしてください。\"],\"ah4fmZ\":[\"YouTube、Vimeo、SoundCloudなどの既知のサービスのプレビューも表示します。\"],\"aifXak\":[\"このチャンネルにはメディアがありません\"],\"ap2zBz\":[\"緩め\"],\"az8lvo\":[\"オフ\"],\"azXSNo\":[\"メンバーリストを展開\"],\"azdliB\":[\"アカウントにログイン\"],\"b26wlF\":[\"彼女/彼女の\"],\"bD/+Ei\":[\"厳格\"],\"bQ6BJn\":[\"詳細なフラッド保護ルールを設定します。各ルールは、監視するアクティビティの種類と、しきい値を超えた場合に実行するアクションを指定します。\"],\"beV7+y\":[\"ユーザーは \",[\"channelName\"],\" への参加招待を受け取ります。\"],\"bk84cH\":[\"離席メッセージ\"],\"bkHdLj\":[\"IRCサーバーを追加\"],\"bmQLn5\":[\"ルールを追加\"],\"bv4cFj\":[\"トランスポート\"],\"bwRvnp\":[\"アクション\"],\"c8+EVZ\":[\"認証済みアカウント\"],\"cGYUlD\":[\"メディアプレビューは読み込まれません。\"],\"cLF98o\":[\"コメントを表示 (\",[\"commentCount\"],\")\"],\"cLKIDO\":[\"利用可能なユーザーがいません\"],\"cSgpoS\":[\"プライベートチャットをピン留め\"],\"cde3ce\":[\"<0>\",[\"0\"],\"</0> へメッセージ\"],\"chQsxg\":[\"フォーマット済み出力をコピー\"],\"cl/A5J\":[[\"__DEFAULT_IRC_SERVER_NAME__\"],\" へようこそ!\"],\"cnGeoo\":[\"削除\"],\"coPLXT\":[\"IRC通信はサーバーに保存されません\"],\"crYH/6\":[\"SoundCloudプレーヤー\"],\"cv5DQb\":[\"ホスト未設定\"],\"d3sis4\":[\"サーバーを追加\"],\"d9aN5k\":[[\"username\"],\" をチャンネルから削除する\"],\"dEgA5A\":[\"キャンセル\"],\"dGi1We\":[\"このプライベートメッセージの会話のピン留めを解除する\"],\"dJVuyC\":[[\"channelName\"],\" を退出しました (\",[\"reason\"],\")\"],\"dMtLDE\":[\"宛て\"],\"dXqxlh\":[\"<0>⚠️ セキュリティリスク!</0> この接続は傍受や中間者攻撃に対して脆弱な可能性があります。\"],\"da9Q/R\":[\"チャンネルモードを変更しました\"],\"dhJN3N\":[\"コメントを表示\"],\"dj2xTE\":[\"通知を閉じる\"],\"dpCzmC\":[\"フラッド保護設定\"],\"e9dQpT\":[\"このリンクを新しいタブで開きますか?\"],\"ePK91l\":[\"編集\"],\"eYBDuB\":[\"画像をアップロードするか、動的サイズ変換のための \",[\"size\"],\" 置換を含むURLを入力してください\"],\"edBbee\":[[\"username\"],\" をホストマスクでBANする(同じIP/ホストからの再参加を防止)\"],\"ekfzWq\":[\"ユーザー設定\"],\"elPDWs\":[\"IRCクライアントの使い心地をカスタマイズする\"],\"eu2osY\":[\"<0>💡 推奨事項:</0> このサーバーを信頼し、リスクを理解している場合のみ続行してください。この接続で機密情報やパスワードを共有しないようにしてください。\"],\"euEhbr\":[\"クリックして \",[\"channel\"],\" に参加\"],\"ez3vLd\":[\"複数行入力を有効にする\"],\"f0J5Ki\":[\"サーバー間通信に暗号化されていない接続が使用される可能性があります\"],\"f9BHJk\":[\"ユーザーに警告\"],\"fDOLLd\":[\"チャンネルが見つかりません。\"],\"ffzDkB\":[\"匿名分析:\"],\"fq1GF9\":[\"ユーザーがサーバーから切断したときに表示\"],\"gEF57C\":[\"このサーバーは1種類の接続タイプのみサポートしています\"],\"gJuLUI\":[\"無視リスト\"],\"gNzMrk\":[\"現在のアバター\"],\"gjPWyO\":[\"ニックネームを入力...\"],\"gz6UQ3\":[\"最大化\"],\"h6/IMX\":[\"最初のネットワークを追加\"],\"h6razj\":[\"チャンネル名マスクを除外\"],\"hG6jnw\":[\"トピックが設定されていません\"],\"hG89Ed\":[\"画像\"],\"hYgDIe\":[\"作成\"],\"hZ6znB\":[\"ポート\"],\"ha+Bz5\":[\"例:100:1440\"],\"he3ygx\":[\"コピー\"],\"hehnjM\":[\"数量\"],\"hzdLuQ\":[\"Voice以上の権限を持つユーザーのみ発言できます\"],\"i0qMbr\":[\"ホーム\"],\"iDNBZe\":[\"通知\"],\"iH8pgl\":[\"戻る\"],\"iL9SZg\":[\"ユーザーをBAN(ニックネーム)\"],\"iNt+3c\":[\"画像に戻る\"],\"iQvi+a\":[\"このサーバーのリンクセキュリティが低い場合に警告しない\"],\"iSLIjg\":[\"接続\"],\"iWXkHH\":[\"Halfop\"],\"iZeTtp\":[\"サーバーホスト\"],\"idD8Ev\":[\"保存済み\"],\"iivqkW\":[\"サインイン日時\"],\"ij+Elv\":[\"画像プレビュー\"],\"ilIWp7\":[\"通知を切り替え\"],\"iuaqvB\":[\"ワイルドカードには * を使用してください。例:baduser!*@*、*!*@spammer.com、troll*!*@*\"],\"ixkTse\":[\"ボット\"],\"j2DGR0\":[\"ホストマスクでBAN\"],\"jA4uoI\":[\"トピック:\"],\"jLXxGK\":[\"https://example.com\"],\"jPSk57\":[\"理由(任意)\"],\"jUV7CU\":[\"アバターをアップロード\"],\"jW5Uwh\":[\"外部メディアの読み込み範囲を制御します。オフ/安全/信頼できるソース/すべてのコンテンツ。\"],\"jXzms5\":[\"添付オプション\"],\"jZlrte\":[\"カラー\"],\"jfC/xh\":[\"連絡先\"],\"jywMpv\":[\"#new-channel-name\"],\"k112DD\":[\"古いメッセージを読み込む\"],\"k3ID0F\":[\"メンバーをフィルター…\"],\"k65gsE\":[\"詳細を見る\"],\"k7Zgob\":[\"接続をキャンセル\"],\"kAVx5h\":[\"招待が見つかりません\"],\"kCLEPU\":[\"接続先\"],\"kF5LKb\":[\"無視パターン:\"],\"kGeOx/\":[[\"0\"],\" に参加\"],\"kITKr8\":[\"チャンネルモードを読み込み中...\"],\"kPpPsw\":[\"あなたはIRC Operatorです\"],\"kWJmRL\":[\"あなた\"],\"kfcRb0\":[\"アバター\"],\"kjMqSj\":[\"JSONをコピー\"],\"krViRy\":[\"JSONとしてコピーするにはクリック\"],\"ks71ra\":[\"例外\"],\"kw4lRv\":[\"チャンネルハーフオペレーター\"],\"kxgIRq\":[\"チャンネルを選択または追加して始めましょう。\"],\"ky6dWe\":[\"アバタープレビュー\"],\"l+GxCv\":[\"チャンネルを読み込み中...\"],\"l+IUVW\":[[\"account\"],\" のアカウント確認に成功しました: \",[\"message\"]],\"l/siQz\":[[\"reconnectCount\",\"plural\",{\"other\":[[\"reconnectCount\"],\"回 再接続した\"]}]],\"l1l8sj\":[[\"0\"],\"日前\"],\"l5NhnV\":[\"#チャンネル(任意)\"],\"l5jmzx\":[[\"0\"],\" と \",[\"1\"],\" が入力中...\"],\"lCF0wC\":[\"更新\"],\"lHy8N5\":[\"さらにチャンネルを読み込み中...\"],\"lasgrr\":[\"使用済み\"],\"lbpf14\":[[\"value\"],\"に参加\"],\"lfFsZ4\":[\"チャンネル\"],\"lkNdiH\":[\"アカウント名\"],\"ln500L\":[\"ObsidianIRC\"],\"lnCMdg\":[\"画像をアップロード\"],\"loQxaJ\":[\"戻りました\"],\"lvfaxv\":[\"ホーム\"],\"m0oxpP\":[\"Libera Chat\"],\"m16xKo\":[\"追加\"],\"m8flAk\":[\"プレビュー(未アップロード)\"],\"mEPxTp\":[\"<0>⚠️ 注意!</0> 信頼できる送信元のリンクのみ開いてください。悪意のあるリンクはセキュリティやプライバシーを侵害する恐れがあります。\"],\"mHGdhG\":[\"サーバー情報\"],\"mHS8lb\":[\"#\",[\"0\"],\" へメッセージ\"],\"mIibu3\":[\"標準ではない WebSocket エンドポイント (例: リバースプロキシ越しの soju) の場合は、パスを含む完全な URL を入力してください: <0>wss://host:port/socket</0>。\"],\"mMYBD9\":[\"広め — より広範な保護範囲\"],\"mTGsPd\":[\"チャンネルトピック\"],\"mU8j6O\":[\"外部メッセージ禁止 (+n)\"],\"mZp8FL\":[\"自動的に1行入力に切り替え\"],\"mdQu8G\":[\"YourNickname\"],\"miSSBQ\":[\"コメント (\",[\"commentCount\"],\")\"],\"mvyLSy\":[\"ユーザーは認証済みです\"],\"mwtcGl\":[\"コメントを閉じる\"],\"myL0MR\":[\"このネットワークを削除しますか?\"],\"mzI/c+\":[\"ダウンロード\"],\"n3fGRk\":[[\"0\"],\" が設定\"],\"nE9jsU\":[\"緩め — 控えめな保護\"],\"nNflMD\":[\"チャンネルを退出\"],\"nPXkBi\":[\"WHOISデータを読み込み中...\"],\"nQnxxF\":[\"#\",[\"0\"],\" へメッセージ(Shift+Enterで改行)\"],\"nWMRxa\":[\"ピン留めを解除\"],\"nkC032\":[\"フラッドプロファイルなし\"],\"o69z4d\":[[\"username\"],\" に警告メッセージを送る\"],\"o9ylQi\":[\"GIFを検索して始めましょう\"],\"oFGkER\":[\"サーバー通知\"],\"oOi11l\":[\"最下部にスクロール\"],\"oPYIL5\":[\"ネットワーク\"],\"oQEzQR\":[\"新しいDM\"],\"oXOSPE\":[\"オンライン\"],\"oal760\":[\"サーバーリンクへの中間者攻撃が可能な状態です\"],\"oeqmmJ\":[\"信頼できるソース\"],\"optX0N\":[[\"0\"],\"時間前\"],\"ovBPCi\":[\"デフォルト\"],\"p0Z69r\":[\"パターンを空にすることはできません\"],\"p1KgtK\":[\"音声の読み込みに失敗しました\"],\"p59pEv\":[\"詳細情報\"],\"p7sRI6\":[\"入力中であることを他のユーザーに知らせる\"],\"pBm1od\":[\"シークレットチャンネル\"],\"pNmiXx\":[\"すべてのサーバーで使用するデフォルトのニックネーム\"],\"pUUo9G\":[\"ホスト名:\"],\"pVGPmz\":[\"アカウントパスワード\"],\"peNE68\":[\"永続的\"],\"plhHQt\":[\"データなし\"],\"pm6+q5\":[\"セキュリティ警告\"],\"pn5qSs\":[\"追加情報\"],\"q0cR4S\":[\"は現在 **\",[\"newNick\"],\"** として知られています\"],\"qFcunY\":[\"LIST または NAMES コマンドにチャンネルが表示されません\"],\"qLpTm/\":[\"リアクション \",[\"emoji\"],\" を削除\"],\"qVkGWK\":[\"ピン留め\"],\"qY8wNa\":[\"ホームページ\"],\"qb0xJ7\":[\"ワイルドカードを使用してください:* は任意の文字列、? は任意の1文字に一致します。例:nick!*@*、*!*@host.com、*!*user@*\"],\"qhzpRq\":[\"チャンネルキー (+k)\"],\"qtoOYG\":[\"制限なし\"],\"r1W2AS\":[\"ファイルホスト画像\"],\"rIPR2O\":[\"トピック設定日時(以前、分前)\"],\"rMMSYo\":[\"最大文字数は \",[\"0\"],\" 文字です\"],\"rWtzQe\":[\"ネットワークが分割され、再接続されました。✅\"],\"rYG2u6\":[\"しばらくお待ちください...\"],\"rdUucN\":[\"プレビュー\"],\"rjGI/Q\":[\"プライバシー\"],\"rk8iDX\":[\"GIFを読み込み中...\"],\"rn6SBY\":[\"ミュート解除\"],\"s/UKqq\":[\"チャンネルからキックされました\"],\"s7oqXR\":[\"ネットワークを選択\"],\"s8cATI\":[[\"channelName\"],\" に参加しました\"],\"sCO9ue\":[\"<0>\",[\"serverName\"],\"</0> への接続には次のセキュリティ上の懸念事項があります:\"],\"sGH11W\":[\"サーバー\"],\"sHI1H+\":[\"は現在 **\",[\"newNick\"],\"** として知られています\"],\"sJyV04\":[[\"inviter\"],\" があなたを \",[\"channel\"],\" に招待しました\"],\"sUBSbK\":[\"アップストリームネットワークがまだありません。\"],\"sby+1/\":[\"クリックしてコピー\"],\"sfN25C\":[\"本名またはフルネーム\"],\"sliuzR\":[\"リンクを開く\"],\"sqrO9R\":[\"カスタムメンション\"],\"sr6RdJ\":[\"Shift+Enterで複数行入力\"],\"swrCpB\":[[\"user\"],\" がチャンネルを \",[\"oldName\"],\" から \",[\"newName\"],\" に変更しました\",[\"0\"]],\"sxkWRg\":[\"詳細設定\"],\"t/YqKh\":[\"削除\"],\"t47eHD\":[\"このサーバーでの固有識別子\"],\"tAkAh0\":[\"動的サイズ変換のための \",[\"size\"],\" 置換を含むURL。例:https://example.com/avatar/\",[\"size\"],\"/channel.jpg\"],\"tXLJS3\":[\"チャンネルリストのサイドバーを表示/非表示にする\"],\"tfDRzk\":[\"保存\"],\"tiBsJk\":[[\"channelName\"],\" を退出しました\"],\"tt4/UD\":[\"退出しました (\",[\"reason\"],\")\"],\"u0TcnO\":[\"ニックネーム {nick} は既に使用中です。{newNick} で再試行します\"],\"u0a8B4\":[\"管理者アクセスのためにIRC Operatorとして認証する\"],\"u0rWFU\":[\"作成日時(以降、分前)\"],\"u72w3t\":[\"無視するユーザーとパターン\"],\"u7jc2L\":[\"退出しました\"],\"uAQUqI\":[\"ステータス\"],\"uB85T3\":[\"保存失敗:\",[\"msg\"]],\"uV3DOL\":[\"G-Line\"],\"uW3lLI\":[\"IRCサーバー:\"],\"ukyW4o\":[\"あなたの招待リンク\"],\"usSSr/\":[\"ズームレベル\"],\"v7uvcf\":[\"ソフトウェア:\"],\"vE8kb+\":[\"Shift+Enterで改行(Enterで送信)\"],\"vERlcd\":[\"プロフィール\"],\"vK0RL8\":[\"トピックなし\"],\"vSJd18\":[\"動画\"],\"vXIe7J\":[\"言語\"],\"vaHYxN\":[\"本名\"],\"vhjbKr\":[\"離席中\"],\"w/nogd\":[[\"0\"],\" ネットワーク\",[\"1\"],\" — 参加するものを選択\"],\"w4NYox\":[[\"title\"],\" クライアント\"],\"w8xQRx\":[\"無効な値\"],\"wFjjxZ\":[[\"username\"],\" によって \",[\"channelName\"],\" からキックされました (\",[\"reason\"],\")\"],\"wGjaGl\":[\"BAN例外が見つかりません\"],\"wPrGnM\":[\"チャンネル管理者\"],\"wRkP2d\":[\"GIF\"],\"wbm86v\":[\"ユーザーがチャンネルに参加・退出したときに表示する\"],\"whqZ9r\":[\"ハイライトする追加の単語またはフレーズ\"],\"wm7RV4\":[\"通知音\"],\"wz/Yoq\":[\"サーバー間で中継される際にメッセージが傍受される可能性があります\"],\"x3+y8b\":[\"このリンクから登録した人数\"],\"xCJdfg\":[\"クリア\"],\"xOTzt5\":[\"たった今\"],\"xUHRTR\":[\"接続時に自動的にOperatorとして認証する\"],\"xWHwwQ\":[\"BAN一覧\"],\"xYilR2\":[\"メディア\"],\"xbi8D6\":[\"このサーバーは招待リンクに対応していません(<0>obby.world/invitation</0>ケイパビリティが告知されていません)。通常通りチャットは利用できます。このパネルはobbyircdベースのネットワーク用です。\"],\"xceQrO\":[\"安全なWebSocketのみサポートされています\"],\"xdtXa+\":[\"チャンネル名\"],\"xfXC7q\":[\"テキストチャンネル\"],\"xlCYOE\":[\"メッセージを取得中...\"],\"xlhswE\":[\"最小値は \",[\"0\"],\" です\"],\"xq97Ci\":[\"単語またはフレーズを追加...\"],\"xuRqRq\":[\"クライアント制限 (+l)\"],\"xwF+7J\":[[\"0\"],\" が入力中...\"],\"y1eoq1\":[\"リンクをコピー\"],\"yJztBY\":[\"ネットワークを削除\"],\"yNeucF\":[\"このサーバーは拡張プロフィールメタデータ(IRCv3 METADATA拡張)をサポートしていません。アバター、表示名、ステータスなどの追加フィールドは利用できません。\"],\"yPlrca\":[\"チャンネルアバター\"],\"yQE2r9\":[\"読み込み中\"],\"ySU+JY\":[\"your@email.com\"],\"yTX1Rt\":[\"Operユーザー名\"],\"yYOzWD\":[\"ログ\"],\"yfx9Re\":[\"IRC Operatorパスワード\"],\"ygCKqB\":[\"停止\"],\"ymDxJx\":[\"IRC Operatorユーザー名\"],\"yrpRsQ\":[\"名前順で並び替え\"],\"yz7wBu\":[\"閉じる\"],\"zJw+jA\":[\"モードを設定: \",[\"0\"]],\"zbymaY\":[[\"0\"],\"分前\"],\"zebeLu\":[\"Operユーザー名を入力\"],\"zpr0Bw\":[\"GZ-Line\"]}"); No newline at end of file
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Regenerate this Japanese catalog: payload contains widespread mojibake.

Many entries are corrupted (ã..., ç..., broken escapes), so Japanese UI text will render unreadable at runtime. Rebuild src/locales/ja/messages.mjs from a valid UTF-8 source catalog and ensure the compile/write step preserves UTF-8 end-to-end.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/locales/ja/messages.mjs` at line 1, The messages export contains
widespread mojibake due to an encoding/serialization issue; regenerate the
Japanese catalog from a clean UTF-8 source and replace the current JSON payload
in the messages constant so all strings are valid Japanese (no sequences like
"ã..." or broken escapes). Rebuild the localization generation/compile step that
produces the messages variable and ensure the pipeline writes the output file
using UTF-8 (verify JSON.stringify output or your i18n tool emits proper Unicode
and the writeFile/writeStream uses 'utf8'), then run the same JSON.parse(...)
usage as before to confirm the file loads correctly at runtime.

…essions

You joined #linux on Libera (a soju-bouncer-bound child) and that
channel got persisted onto the soju control row -- because the
savedServer lookup in joinChannel keys on host:port, and the
control session, every bound child, and any sibling network all
share the same soju endpoint. On reconnect every one of them tried
to JOIN #linux, including the control connection which doesn't
have channels of its own.

soju already remembers per-network channel state and replays the
JOINs after BIND, so client-side persistence is at best redundant
and at worst destructive. Gate every channel-state write on
`!(isBouncerControl || bouncerNetid)`:

* joinChannel: skip the savedServer.channels.push for bouncer sessions
* leaveChannel: skip the savedServer.channels = … reassignment
* reorderChannels: keep the per-server channelOrder write (keyed by
  serverId, no ambiguity) but skip the savedServer.channels write
* connection.ts ready handler: skip the for-each saved-channel
  joinChannel() replay for bouncer sessions

Standalone (non-bouncer) servers continue to persist + replay as
before.
Rationale moves to commit messages; source keeps only what helps a
reader who walks in cold (invariants, edge cases). Per repo
convention I kept ignoring in earlier commits.

No behaviour change.
…trol

Disconnecting the soju control session now opens an in-app modal
(not window.confirm) listing the bound networks that will close
alongside it, then drops the parent and every child in one action.

* New UI slot ui.disconnectConfirmTarget; requestDeleteServer() routes
  through it for bouncer parents, calls deleteServer() directly
  otherwise.
* deleteServer() collects {parentId, ...children with bouncerServerId=parent}
  up-front, then removes them from ircClient, savedServers, metadata,
  and in-memory state in one set().
* BouncerDisconnectConfirmModal renders shotglass+crown header, lists
  each child as a network card (shotglass+plug chip, friendly name,
  host), and confirm/cancel buttons.
* ServerList's onDelete now calls requestDeleteServer.
* deleteServerBouncer test updated: parent delete is now expected to
  cascade (was previously expected to leave children behind, when
  there was no UI path to that anyway).
* 4 strings × 18 locales translated.
…ss badge

* Propagate ISUPPORT NETWORK from ircClient into the Zustand Server
  row (set Server.networkName) so the channel-list header can
  resolve the upstream identity ("Libera.Chat") instead of falling
  back to the seeded child name ("soju/1") when the bouncer's
  NETWORK attribute is missing.
* ChannelList header gets the shotglass+crown chip on the control
  session and shotglass+plug on every bound child, matching the
  sidebar tile badge. Title remains networkName || name.
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